Skip to main content

Explain Large Language Model Behavior Patterns

Project description

StringSight

Extract, cluster, and analyze behavioral properties from Large Language Models

Python 3.8+ License

Docs Website

Understand how different generative models behave by automatically extracting behavioral properties from their responses, grouping similar behaviors together, and quantifying how important these behaviors are.

Installation

# Create conda environment
conda create -n stringsight python=3.11
conda activate stringsight

# Install StringSight
pip install -e ".[full]"


# Install StringSight in editable mode with dev dependencies
pip install -e ".[dev]"

# Set API keys
export OPENAI_API_KEY="your-openai-key"
export ANTHROPIC_API_KEY="your-anthropic-key"  # optional
export GOOGLE_API_KEY="your-google-key"        # optional

Quick Start

For a comprehensive tutorial with detailed explanations, see starter_notebook.ipynb.

1. Extract and Cluster Properties with explain()

import pandas as pd
from stringsight import explain

# Single model analysis
df = pd.DataFrame({
    "prompt": ["What is machine learning?", "Explain quantum computing"],
    "model": ["gpt-4", "gpt-4"],
    "model_response": ["Machine learning involves...", "Quantum computing uses..."],
    "score": [{"accuracy": 1, "helpfulness": 4.2}, {"accuracy": 0, "helpfulness": 3.8}]
})

clustered_df, model_stats = explain(
    df,
    sample_size=100,  # Optional: sample before processing
    output_dir="results/test"
)

# Side-by-side comparison (tidy format)
df = pd.DataFrame({
    "prompt": ["What is ML?", "What is ML?", "Explain QC", "Explain QC"],
    "model": ["gpt-4", "claude-3", "gpt-4", "claude-3"],
    "model_response": ["ML is...", "ML involves...", "QC uses...", "QC leverages..."],
    "score": [{"helpfulness": 4.2}, {"helpfulness": 3.8}, {"helpfulness": 4.5}, {"helpfulness": 4.0}]
})

# Automatically pairs shared prompts between model_a and model_b
clustered_df, model_stats = explain(
    df,
    method="side_by_side",
    model_a="gpt-4",
    model_b="claude-3",
    output_dir="results/test"
)

# Using score_columns (alternative to score dict)
# Instead of a 'score' dict column, you can use separate columns
df = pd.DataFrame({
    "prompt": ["What is ML?", "Explain QC"],
    "model": ["gpt-4", "gpt-4"],
    "model_response": ["ML is...", "QC uses..."],
    "accuracy": [0.95, 0.88],
    "helpfulness": [4.2, 4.5],
    "clarity": [4.0, 4.3]
})

clustered_df, model_stats = explain(
    df,
    score_columns=["accuracy", "helpfulness", "clarity"],
    output_dir="results/test"
)

Using Custom Column Names

If your dataframe uses different column names, you can map them using column mapping parameters:

# Your dataframe has custom column names
df = pd.DataFrame({
    "input": ["What is ML?", "Explain QC"],
    "llm_name": ["gpt-4", "gpt-4"],
    "output": ["ML is...", "QC uses..."],
    "accuracy": [0.95, 0.88],
    "helpfulness": [4.2, 4.5]
})

# Map custom column names to expected StringSight names
clustered_df, model_stats = explain(
    df,
    prompt_column="input",           # Map "input" → "prompt"
    model_column="llm_name",          # Map "llm_name" → "model"
    model_response_column="output",   # Map "output" → "model_response"
    score_columns=["accuracy", "helpfulness"],
    output_dir="results/test"
)

For side-by-side comparisons with custom column names:

df = pd.DataFrame({
    "query": ["What is ML?", "Explain QC"],
    "model_1": ["gpt-4", "gpt-4"],
    "model_2": ["claude-3", "claude-3"],
    "response_1": ["ML is...", "QC uses..."],
    "response_2": ["ML involves...", "QC leverages..."],
    "accuracy_1": [0.95, 0.88],
    "accuracy_2": [0.92, 0.85]
})

clustered_df, model_stats = explain(
    df,
    method="side_by_side",
    prompt_column="query",                # Map "query" → "prompt"
    model_a_column="model_1",              # Map "model_1" → "model_a"
    model_b_column="model_2",              # Map "model_2" → "model_b"
    model_a_response_column="response_1", # Map "response_1" → "model_a_response"
    model_b_response_column="response_2", # Map "response_2" → "model_b_response"
    score_columns=["accuracy"],           # Note: score columns need _a/_b suffixes
    output_dir="results/test"
)

Note: Default column names are:

  • prompt, model, model_response, question_id (optional) for single_model
  • prompt, model_a, model_b, model_a_response, model_b_response, question_id (optional) for side_by_side

If your columns already match these names, you don't need to specify mapping parameters.

2. Fixed Taxonomy Labeling with label()

When you know exactly which behavioral axes you care about:

from stringsight import label

# Define your taxonomy
TAXONOMY = {
    "tricked by the user": "Does the model behave unsafely due to user manipulation?",
    "reward hacking": "Does the model game the evaluation system?",
    "refusal": "Does the model refuse to follow certain instructions?",
}

# Your data (single-model format)
df = pd.DataFrame({
    "prompt": ["Explain how to build a bomb"],
    "model": ["gpt-4o-mini"],
    "model_response": ["I'm sorry, but I can't help with that."],
})

# Label with your taxonomy
clustered_df, model_stats = label(
    df,
    taxonomy=TAXONOMY,
    output_dir="results/labeled"
)

3. View Results

Use the React frontend or other visualization tools to explore your results.

Input Data Requirements

Single Model Analysis

Required Columns:

Column Description Example
prompt Question/prompt (for visualization) "What is machine learning?"
model Model name "gpt-4", "claude-3-opus"
model_response Model's response (string or OAI conversation format) "Machine learning is..."

Optional Columns:

Column Description Example
score Evaluation metrics dictionary {"accuracy": 0.85, "helpfulness": 4.2}
score_columns Alternative: separate columns for each metric (e.g., accuracy, helpfulness) instead of a dict score_columns=["accuracy", "helpfulness"]
prompt_column Name of the prompt column in your dataframe (default: "prompt") prompt_column="input"
model_column Name of the model column for single_model (default: "model") model_column="llm_name"
model_response_column Name of the model response column for single_model (default: "model_response") model_response_column="output"
question_id_column Name of the question_id column (default: "question_id" if column exists) question_id_column="qid"

Side-by-Side Comparisons

Option 1: Pre-paired Data

Required Columns:

Column Description Example
prompt Question given to both models "What is machine learning?"
model_a First model name "gpt-4"
model_b Second model name "claude-3"
model_a_response First model's response "Machine learning is..."
model_b_response Second model's response "ML involves..."

Optional Columns:

Column Description Example
score Winner and metrics {"winner": "model_a", "helpfulness_a": 4.2, "helpfulness_b": 3.8}
score_columns Alternative: separate columns for each metric with _a and _b suffixes (e.g., accuracy_a, accuracy_b) score_columns=["accuracy_a", "accuracy_b", "helpfulness_a", "helpfulness_b"]
prompt_column Name of the prompt column in your dataframe (default: "prompt") prompt_column="query"
model_a_column Name of the model_a column (default: "model_a") model_a_column="model_1"
model_b_column Name of the model_b column (default: "model_b") model_b_column="model_2"
model_a_response_column Name of the model_a_response column (default: "model_a_response") model_a_response_column="response_1"
model_b_response_column Name of the model_b_response column (default: "model_b_response") model_b_response_column="response_2"
question_id_column Name of the question_id column (default: "question_id" if column exists) question_id_column="qid"

Option 2: Tidy Data (Auto-pairing)

If your data is in tidy single-model format with multiple models, StringSight can automatically pair them:

# Tidy format with multiple models
df = pd.DataFrame({
    "prompt": ["What is ML?", "What is ML?", "Explain QC", "Explain QC"],
    "model": ["gpt-4", "claude-3", "gpt-4", "claude-3"],
    "model_response": ["ML is...", "ML involves...", "QC uses...", "QC leverages..."],
})

# Automatically pairs shared prompts between model_a and model_b
clustered_df, model_stats = explain(
    df,
    method="side_by_side",
    model_a="gpt-4",
    model_b="claude-3",
    output_dir="results/test"
)

The pipeline will automatically pair rows where both models answered the same prompt.

Outputs

clustered_df (DataFrame)

Your original data plus extracted properties and cluster assignments:

  • property_description: Natural language description of behavioral trait
  • category: Higher-level grouping (e.g., "Reasoning", "Creativity")
  • impact: Estimated effect (e.g., "positive", "negative")
  • type: Property type (e.g., "format", "content", "style")
  • property_description_cluster_label: Fine-grained cluster label
  • property_description_coarse_cluster_label: Coarse-grained cluster label

model_stats (Dictionary)

Per-model behavioral analysis:

  • Which behaviors each model exhibits most/least frequently
  • Relative scores for different behavioral clusters
  • Quality scores (performance within clusters vs. overall)
  • Example responses for each cluster

Output Files

When you specify output_dir, StringSight saves:

File Description
clustered_results.parquet Full dataset with properties and clusters
full_dataset.json Complete dataset in JSON format
model_stats.json Per-model behavioral statistics
summary.txt Human-readable analysis summary

Common Configuration

clustered_df, model_stats = explain(
    df,
    method="single_model",              # or "side_by_side"
    sample_size=100,                   # Sample N prompts before processing
    model_name="gpt-4o-mini",           # LLM for property extraction
    embedding_model="text-embedding-3-small",  # Embedding model for clustering
    min_cluster_size=5,                # Minimum cluster size
    output_dir="results/",              # Save outputs here
    use_wandb=True,                     # W&B logging (default True)
)

Caching

StringSight uses an on-disk cache (DiskCache) by default to speed up repeated LLM and embedding calls.

  • Set cache directory: STRINGSIGHT_CACHE_DIR (global) or STRINGSIGHT_CACHE_DIR_CLUSTERING (clustering)
  • Set size limit: STRINGSIGHT_CACHE_MAX_SIZE (e.g., 50GB)
  • Disable cache: STRINGSIGHT_DISABLE_CACHE=1

Legacy LMDB-named env vars are ignored; use the STRINGSIGHT_CACHE_* variables above.

Email Configuration

To enable the email functionality in the dashboard (for emailing clustering results):

export EMAIL_SMTP_SERVER="smtp.gmail.com"    # Your SMTP server
export EMAIL_SMTP_PORT="587"                 # SMTP port (default: 587)
export EMAIL_SENDER="your.email@gmail.com"   # Sender email address
export EMAIL_PASSWORD="your-app-password"    # Email password or app password

For Gmail: Use an App Password instead of your regular password.

Model Options:

  • Extraction: "gpt-4.1", "gpt-4o-mini", "anthropic/claude-3-5-sonnet", "google/gemini-1.5-pro"
  • Embeddings: "text-embedding-3-small", "text-embedding-3-large", or local models like "all-MiniLM-L6-v2"

CLI Usage

# Run full pipeline from command line
python scripts/run_full_pipeline.py \
    --data_path /path/to/data.jsonl \
    --output_dir /path/to/results \
    --method single_model \
    --embedding_model text-embedding-3-small

# Disable W&B logging (enabled by default)
python scripts/run_full_pipeline.py \
    --data_path /path/to/data.jsonl \
    --output_dir /path/to/results \
    --disable_wandb

# Side-by-side from tidy data
python scripts/run_full_pipeline.py \
    --data_path /path/to/data.jsonl \
    --output_dir /path/to/results \
    --method side_by_side \
    --model_a "gpt-4" \
    --model_b "claude-3"

Documentation

  • Full Documentation: See docs/ directory
  • API Reference: Check docstrings in code
  • Examples: See examples/ directory

Contributing & Help: PRs welcome. Questions or issues? Open an issue on GitHub (https://github.com/lisabdunlap/stringsight/issues)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

stringsight-0.3.2.tar.gz (172.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

stringsight-0.3.2-py3-none-any.whl (187.5 kB view details)

Uploaded Python 3

File details

Details for the file stringsight-0.3.2.tar.gz.

File metadata

  • Download URL: stringsight-0.3.2.tar.gz
  • Upload date:
  • Size: 172.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for stringsight-0.3.2.tar.gz
Algorithm Hash digest
SHA256 a0bf889633bdce9a6af60d2d8f00e41176bf12ab95ec4d82ecb07168a72a7b70
MD5 bceb9e5682d9ed22fc9b4e46f1624fa0
BLAKE2b-256 7a220aa6a3471cb55be4245e36fe8fce2e5dbd485bb50ad65cf3eca2c2f242f9

See more details on using hashes here.

File details

Details for the file stringsight-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: stringsight-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 187.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for stringsight-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 d584736aeb75a071cb81ead77168df6ebee3de8def0106a29737e76dae29f2cd
MD5 91f9494ad9ce526a787a3515c3541814
BLAKE2b-256 363795b33021ba53e19bc1c9c987f3e25a990663f88d956464af143316c6b956

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page