Skip to main content

Utility-Lens

dev-version 0.1.0c

Is your language model a rational decision maker? Does it consistently prefer $100 over $50? Will it still prefer a sandwich over an apple when a banana is available? Can we extract meaningful utility values from its choices?

Utility-Lens helps answer these questions by testing language models against the classical axioms of rational choice theory and extracting underlying utility functions from their revealed preferences.

Core Analyses

  1. Transitivity: Does your model make logically consistent choices?
  2. Independence of Irrelevant Alternatives (IIA): Does your model maintain its preferences when new alternatives are introduced?
  3. Utility Estimation: What cardinal utilities best explain your model's choices?

Installation

pip install utility-lens

Demos

A. Testing Transitivity

Check if your model's preferences form a rational ordering:

Code
from utility_lens import OpenAIModel, TransitivityAnalyzer

# Required: OpenAI API key
openai_api_key = ''

# Required: List of items to compare
animals = [ "elephant", "human", "chimpanzee", "komodo", ...]

###################
# Initialize model#
###################
model = OpenAIModel(
    # Required parameters:
    model_name="gpt-3.5-turbo-0125",  # Required: Name of model to use
    api_key=openai_api_key,           # Required: OpenAI API key

    # Optional parameters (with defaults):
    base_url=None,         # Optional (default=None): Base URL for API 
    max_tokens=10,         # Optional (default=10): Max tokens in response
    concurrency_limit=100, # Optional (default=50): Max concurrent calls
)

#########################
# Initialize analyzer   #
#########################
analyzer = TransitivityAnalyzer(
    # Required parameters:
    model=model,              # Required: Model instance to use
    items=animals,             # Required: List of items to compare

    # Optional parameters (with defaults):
    n_trial=10,             # Optional (default=10): # API calls per pair
                            #  (how many times to ask the same question)
    n_triad=200,            # Optional (default=200): Number of triads
                            # Use -1 for all possible triads
    seed=42,                # Optional (default=42): Random seed
    save_directory="results"# Optional (default=None): Dir to save results
                            # None means don't save results
)

############################
# Run transitivity analysis#
############################
results = analyzer.run(
    use_async=True  # Optional (default=True): Processing mode
                    # True = Concurrent processing (faster but needs async support)
                    # False = Sequential processing (works everywhere)
)

#################################
# Print key transitivity metrics#
#################################
print("\nTransitivity Analysis Results:")
print(f"Overall transitivity score: {results['transitivity_score']:.3f}")
print(f"Weak stochastic transitivity: {results['weak_stochastic_transitivity_satisfied']}")
print(f"Strong stochastic transitivity: {results['strong_stochastic_transitivity_satisfied']}")

# Print top cycles (if any)
print("\nTop preference cycles found:")
for cycle in results['possible_cycles'][:3]:  # Show top 3 cycles
    print(f"\nProbability: {cycle['probability']:.3f}")
    print(f"Path: {cycle['cycle_path']}")
    print(f"Items involved: {cycle['triad']}")

############################################
# Results dictionary structure explanation #
############################################

# Results structure:
# {
#    'transitivity_score': float,    # Overall transitivity (0-1)
#                                   # 1 = perfectly transitive
#                                   # 0 = completely cyclic
#
#    'weak_stochastic_transitivity_satisfied': str,  # Format: "X/Y"
#                                   # X = number of triads satisfying WST
#                                   # Y = total triads tested
#
#    'strong_stochastic_transitivity_satisfied': str,  # Format: "X/Y"
#                                   # X = number of triads satisfying SST
#                                   # Y = total triads tested
#
#    'possible_cycles': List[Dict],  # List of detected preference cycles
#                                   # Sorted by probability (highest first)
#                                   # Each dict contains:
#                                   # - 'probability': float
#                                   # - 'cycle_path': str description
#                                   # - 'triad': List[str] items involved
#
#    'triad_results': List[Dict],   # Detailed results for each triad
#                                   # Including preference strengths and
#                                   # transitivity violations
#
#    'raw_data': List[Dict]         # Raw comparison data from model
# }

B. Testing IIA

Verify if preferences remain stable with new options:

Code
from utility_lens import OpenAIModel, IIAAnalyzer

# Required: OpenAI API key
openai_api_key = ''

# Required: List of items to compare
animals = [ "elephant", "human", "chimpanzee", "komodo", ...]

###################
# Initialize model#
###################
model = OpenAIModel(
    # Required parameters:
    model_name="gpt-3.5-turbo-0125",  # Required: Name of model to use
    api_key=openai_api_key,           # Required: OpenAI API key

    # Optional parameters (with defaults):
    base_url=None,         # Optional (default=None): Base URL for API 
    max_tokens=10,         # Optional (default=10): Max tokens in response
    concurrency_limit=100, # Optional (default=50): Max concurrent calls
)

#########################
# Initialize analyzer   #
#########################
analyzer = IIAAnalyzer(
    # Required parameters:
    model=model,            # Required: Model instance to use
    items=animals,          # Required: List of items to compare

    # Optional parameters (with defaults):
    n_trial=10,             # Optional (default=10): # API calls per pair
                            #  (how many times to ask the same question)
    n_pairs=-1,             # Default=200: Use 200 out of all possible  
                            #  pairs. Can also use -1 for all pairs or 
                            #  specify a number
    seed=42,                # Optional (default=42): Random seed
    threshold=0.1,          # Optional (default=0.1): IIA threshold
    save_directory="results"# Optional (default=None): Dir to save results
                            # None means don't save results
)

########################
# Run IIA analysis     #
########################
results = analyzer.run(
    use_async=True  # Optional (default=True): Use async processing
)

#########################
# Print IIA metrics     #
#########################
print("\nIIA Analysis Results:")
print(f"Overall IIA score: {results['iia_score_I']:.3f}")

############################################
# Results dictionary structure explanation #
############################################

# Results structure:
# {
#    'iia_score': float,        # Overall IIA satisfaction score (0-1)
#                               # 1 = perfect IIA satisfaction
#                               # 0 = complete IIA violation
#
#    'stable_preferences': int, # Number of pairs with stable preferences
#    'total_pairs': int,        # Total pairs tested
#
#    'violations': List[Dict],  # List of IIA violations
#                               # Sorted by magnitude (largest first)
#                               # Each dict contains:
#                               # - 'base_pair': (str, str)
#                               # - 'original_preference': float
#                               # - 'context_item': str
#                               # - 'new_preference': float
#                               # - 'shift': float
#
#    'pair_results': List[Dict],# Detailed results for each pair
#                               # Including preference strengths
#
#    'raw_data': List[Dict]     # Raw comparison data from model
# }

C. Extracting Utilities

Compute the underlying utilities that best explain the observed choices:

Code - Using Bradley-Terry
from utility_lens import OpenAIModel, UtilityAnalyzer
import numpy as np

# Required: OpenAI API key
openai_api_key = ''

# Required: List of items to compare
animals = [ "elephant", "human", "chimpanzee", "komodo", ...]

###################
# Initialize model#
###################
model = OpenAIModel(
    # Required parameters:
    model_name="gpt-3.5-turbo-0125",  # Required: Name of model to use
    api_key=openai_api_key,           # Required: OpenAI API key

    # Optional parameters (with defaults):
    base_url=None,        # Optional (default=None): Base URL for API 
    max_tokens=10,        # Optional (default=10): Max tokens in response
    concurrency_limit=100,# Optional (default=50): Max concurrent calls
                          #  Doesn't mean much unless using async
)

#########################
# Initialize analyzer   #
#########################
analyzer = UtilityAnalyzer(
    # Required parameters:
    model=model,            # Required: Model instance to use
    items=animals,          # Required: List of items to compare

    # Optional parameters (with defaults):
    n_trial=10,             # Optional (default=10): # samples per pair 
    n_pairs=-1,             # Default=200: Use 200 out of all possible  
                            #  pairs. Can also use -1 or None for all 
                            #  pairs or specify a number
    seed=42,                # Optional (default=42): Random seed 
    save_directory="results"# Optional (default=None): Set to save 
                            #  None means don't save results
)

################################
# Run Bradley-Terry analysis   #
################################
bt_results = analyzer.run(
    # All parameters are optional with defaults shown:
    method="bradley-terry", # Optional (default="bradley-terry")
                            #  Model type to use
    use_soft_labels=True,   # Optional (default=True): Use ratios vs binary
                            #  True = actual ratios (e.g., 7:3)
                            #  False = binary preferences (e.g., 1 or 0)
    num_epochs=1000,        # Optional (default=1000): Number of training epchs
    learning_rate=0.01,     # Optional (default=0.01): LR for optimization
    use_async=True          # Optional (default=True): Processing mode
                            #  False: Sequential (works everywhere)
                            #  True: Concurrent(faster but needs async support)
)

# Print Bradley-Terry rankings
print("\nBradley-Terry Rankings:")
print(f"Model accuracy: {bt_results['accuracy']:.3f}")
print("\nUtility Rankings:")
for item, utility in bt_results['rankings']:
    print(f"{item}: {utility:.3f}")

############################################
# Results dictionary structure explanation #
############################################

# Bradley-Terry results structure:
# {
#    'utilities': Dict,    # Maps item index to utility value
#                          # Example: {0: 1.2, 1: 0.8, 2: -0.5}
#    'rankings': List,     # Sorted (item, utility) pairs
#                          # Example: [("elephant", 1.2), ("human", 0.8)]
#    'accuracy': float,    # Model prediction accuracy (0-1)
#    'log_loss': float,    # Model log loss
#    'raw_data': Dict      # Raw preference data collected from model
# }
Code - Using Thurstonian
from utility_lens import OpenAIModel, UtilityAnalyzer
import numpy as np

# Required: OpenAI API key
openai_api_key = ''

# Required: List of items to compare
animals = [ "elephant", "human", "chimpanzee", "komodo", ...]

###################
# Initialize model#
###################
model = OpenAIModel(
    # Required parameters:
    model_name="gpt-3.5-turbo-0125",  # Required: Name of model to use
    api_key=openai_api_key,           # Required: OpenAI API key

    # Optional parameters (with defaults):
    base_url=None,        # Optional (default=None): Base URL for API 
    max_tokens=10,        # Optional (default=10): Max tokens in response
    concurrency_limit=100,# Optional (default=50): Max batch size 
                          #  Doesn't mean much unless using async
)

#########################
# Initialize analyzer   #
#########################
analyzer = UtilityAnalyzer(
    # Required parameters:
    model=model,            # Required: Model instance to use
    items=animals,          # Required: List of items to compare

    # Optional parameters (with defaults):
    n_trial=10,             # Optional (default=10): # samples per pair 
    n_pairs=-1,             # Default=200: Use 200 out of all possible  
                            #  pairs. Can also use -1 for all pairs or 
                            #  specify a number
    seed=42,                # Optional (default=42): Random seed 
    save_directory="results"# Optional (default=None): Set to save 
                            #  None means don't save results
)

#############################
# Run Thurstonian analysis  #
#############################
thurst_results = analyzer.run(
    # All parameters are optional with defaults shown:
    method="thurstonian",   # default="bradley-terry": Model type to use
                            #  Model type to use
    use_soft_labels=True,   # Optional (default=True): Use ratios vs binary
                            #  True = actual ratios (e.g., 7:3)
                            #  False = binary preferences (e.g., 1 or 0)
    num_epochs=1000,        # Optional (default=1000): Number of training epchs
    learning_rate=0.01,     # Optional (default=0.01): LR for optimization
    use_async=True          # Optional (default=True): Processing mode
                            #  False: Sequential (works everywhere)
                            #  True: Concurrent(faster but needs async support)
)


# Print Thurstonian rankings with uncertainty
print("\nThurstonian Rankings:")
print(f"Model accuracy: {thurst_results['accuracy']:.3f}")
print("\nUtility Rankings (mean ± std):")
for item, stats in thurst_results['rankings']:
    mean = stats['mean']
    std = np.sqrt(stats['variance'])
    print(f"{item}: {mean:.3f} ± {std:.3f}")

############################################
# Results dictionary structure explanation #
############################################

# Thurstonian results structure:
# {
#    'utilities': Dict,   # Maps item index to mean and variance
#                         # Example: {0: {'mean': 1.2, 'variance': 0.1}}
#    'rankings': List,    # Sorted by mean utility
#                         # Example: [("..", {'mean': 1, 'variance': 1})]
#    'accuracy': float,   # Model prediction accuracy (0-1)
#    'log_loss': float,   # Model log loss
#    'raw_data': Dict     # Raw preference data collected from model
# }

Metadata

Release files for utility-lens 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for utility-lens 0.1.3
File Size Uploaded Provenance Actions
utility_lens-0.1.3.tar.gz 21.6 kB Without provenance Details for utility_lens-0.1.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for utility-lens 0.1.3
File Interpreter ABI Platform Actions
utility_lens-0.1.3-py3-none-any.whl Python 3 none any Details for utility_lens-0.1.3-py3-none-any.whl

Total release size: 45.1 kB

Release files / utility_lens-0.1.3.tar.gz

Download URL utility_lens-0.1.3.tar.gz
Size 21.6 kB
Tags Source
SHA-256 checksum
How to use checksums
3a3cb47030a393e584b56925edb4e4429fa362208f07cb468a4b112eee5d2356
BLAKE2b-256 checksum
How to use checksums
752397cd9e8de9262f85126e0ce0ecf598d6129731d553690c7c65117cd302ab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.8.3 CPython/3.13.0 Darwin/23.3.0

Release files / utility_lens-0.1.3-py3-none-any.whl

Download URL utility_lens-0.1.3-py3-none-any.whl
Size 23.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
32723280251c71a35706ec02c6d96177c841e4883feb25293ad2062231a738b3
BLAKE2b-256 checksum
How to use checksums
930f4c7339d26239294ec4d298ec432125783878463f041e5cb1485560f8882f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.8.3 CPython/3.13.0 Darwin/23.3.0

Security for utility-lens 0.1.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page