Skip to contents

Usage Tutorial

This tutorial provides detailed instructions for using mLLMCelltype for cell type annotation in single-cell RNA sequencing data. We’ll cover various usage scenarios, parameter configurations, and integration with Seurat.

Comprehensive Function Parameters

annotate_cell_types()

The main function for cell type annotation with a single model:

library(mLLMCelltype)

results <- annotate_cell_types(
  input,                # Marker gene data (data frame, list, or file path)
  tissue_name,          # Tissue name (e.g., "human PBMC", "mouse brain")
  model,                # LLM model to use
  api_key = NA,         # API key (if not set in environment, NA returns prompt only)
  top_gene_count = 10,  # Number of top genes per cluster to use
  debug = FALSE         # Whether to print debugging information
)

interactive_consensus_annotation()

Function for creating consensus annotations from multiple models through interactive discussion:

consensus_results <- interactive_consensus_annotation(
  input,                # Original marker gene data (Seurat FindAllMarkers result or list of genes)
  tissue_name = NULL,   # Optional tissue name
  models = c("claude-3-7-sonnet-20250219", "gpt-4o", "gemini-1.5-pro"),  # Models to use
  api_keys,             # Named list of API keys
  top_gene_count = 10,  # Number of top genes to use
  controversy_threshold = 0.7,  # Threshold for identifying controversial clusters
  entropy_threshold = 1.0,  # Entropy threshold for controversial clusters
  max_discussion_rounds = 3,  # Maximum discussion rounds
  consensus_check_model = NULL,  # Model to use for consensus checking (see recommendations below)
  log_dir = "logs",     # Directory for logs
  cache_dir = "consensus_cache",  # Directory for cache
  use_cache = TRUE      # Whether to use cache
)

Detailed Usage Scenarios

Scenario 1: Basic Annotation with a Single Model

For quick exploration or when API usage is a concern:

# Load example data
library(Seurat)
data("pbmc_small")

# Find markers
pbmc_markers <- FindAllMarkers(pbmc_small, only.pos = TRUE, min.pct = 0.25, logfc.threshold = 0.25)

# Run annotation with a single model
results <- annotate_cell_types(
  input = pbmc_markers,
  tissue_name = "human PBMC",
  model = "claude-3-7-sonnet-20250219",
  api_key = Sys.getenv("ANTHROPIC_API_KEY"),
  top_gene_count = 10
)

# Add annotations to Seurat object
pbmc_small$cell_type_claude <- plyr::mapvalues(
  x = as.character(Idents(pbmc_small)),
  from = as.character(0:(length(results)-1)),
  to = results
)

# Visualize
DimPlot(pbmc_small, group.by = "cell_type_claude", label = TRUE)

Scenario 2: Multi-Model Consensus for High Accuracy

For publication-quality annotations with uncertainty quantification:

# Define multiple models to use
models <- c(
  "claude-3-7-sonnet-20250219",  # Anthropic
  "gpt-4o",                      # OpenAI
  "gemini-1.5-pro",              # Google
  "grok-3"                       # X.AI
)

# API keys for different providers
api_keys <- list(
  anthropic = Sys.getenv("ANTHROPIC_API_KEY"),
  openai = Sys.getenv("OPENAI_API_KEY"),
  gemini = Sys.getenv("GEMINI_API_KEY"),
  grok = Sys.getenv("GROK_API_KEY")
)

# Run annotation with multiple models
results <- list()
for (model in models) {
  provider <- get_provider(model)
  api_key <- api_keys[[provider]]

  results[[model]] <- annotate_cell_types(
    input = pbmc_markers,
    tissue_name = "human PBMC",
    model = model,
    api_key = api_key,
    top_gene_count = 10
  )
}

# Create consensus
consensus_results <- interactive_consensus_annotation(
  input = pbmc_markers,
  tissue_name = "human PBMC",
  models = models,  # Use all the models defined above
  api_keys = api_keys,
  controversy_threshold = 0.7,
  entropy_threshold = 1.0,
  consensus_check_model = "claude-3-7-sonnet-20250219"
)

# View consensus results
# You can access the final annotations with consensus_results$final_annotations

# Add consensus annotations and metrics to Seurat object
pbmc_small$cell_type_consensus <- plyr::mapvalues(
  x = as.character(Idents(pbmc_small)),
  from = as.character(0:(length(consensus_results$final_annotations)-1)),
  to = consensus_results$final_annotations
)

# Extract consensus metrics from the consensus results
consensus_metrics <- lapply(names(consensus_results$initial_results$consensus_results), function(cluster_id) {
  metrics <- consensus_results$initial_results$consensus_results[[cluster_id]]
  return(list(
    cluster = cluster_id,
    consensus_proportion = metrics$consensus_proportion,
    entropy = metrics$entropy
  ))
})

# Convert to data frame for easier handling
metrics_df <- do.call(rbind, lapply(consensus_metrics, data.frame))

# Add consensus proportion to Seurat object
pbmc_small$consensus_proportion <- plyr::mapvalues(
  x = as.character(Idents(pbmc_small)),
  from = metrics_df$cluster,
  to = metrics_df$consensus_proportion
)

# Add entropy to Seurat object
pbmc_small$shannon_entropy <- plyr::mapvalues(
  x = as.character(Idents(pbmc_small)),
  from = metrics_df$cluster,
  to = metrics_df$entropy
)

Scenario 2b: Using Free OpenRouter Models

For users with limited API credits or budget constraints:

# Set OpenRouter API key
openrouter_api_key <- Sys.getenv("OPENROUTER_API_KEY")

# Define free OpenRouter models to use
free_models <- c(
  "meta-llama/llama-4-maverick:free",                # Meta Llama 4 Maverick (free)
  "nvidia/llama-3.1-nemotron-ultra-253b-v1:free",    # NVIDIA Nemotron Ultra 253B (free)
  "deepseek/deepseek-chat-v3-0324:free",             # DeepSeek Chat v3 (free)
  "microsoft/mai-ds-r1:free"                         # Microsoft MAI-DS-R1 (free)
)

# Run annotation with free OpenRouter models
free_results <- list()
for (model in free_models) {
  free_results[[model]] <- annotate_cell_types(
    input = pbmc_markers,
    tissue_name = "human PBMC",
    model = model,  # OpenRouter models are automatically detected by format: 'provider/model-name:free'
    api_key = openrouter_api_key,
    top_gene_count = 10
  )
}

# Create consensus with free models
free_consensus_results <- interactive_consensus_annotation(
  input = pbmc_markers,
  tissue_name = "human PBMC",
  models = free_models,  # Use all the free models defined above
  api_keys = list("openrouter" = openrouter_api_key),
  controversy_threshold = 0.7,
  entropy_threshold = 1.0,
  consensus_check_model = "meta-llama/llama-4-maverick:free"  # Use a free model for consensus checking
)

# View free model consensus results
# You can access the final annotations with free_consensus_results$final_annotations

# Add free model consensus annotations to Seurat object
pbmc_small$free_model_consensus <- plyr::mapvalues(
  x = as.character(Idents(pbmc_small)),
  from = as.character(0:(length(free_consensus_results$final_annotations)-1)),
  to = free_consensus_results$final_annotations
)

# Compare paid vs. free model results
comparison <- data.frame(
  cluster = as.character(0:(length(consensus_results$final_annotations)-1)),
  paid_models = consensus_results$final_annotations,
  free_models = free_consensus_results$final_annotations,
  agreement = consensus_results$final_annotations == free_consensus_results$final_annotations
)
print(comparison)

Scenario 3: Working with CSV Files

For users who prefer working with files:

# Save markers to CSV
write.csv(pbmc_markers, "pbmc_markers.csv", row.names = FALSE)

# Run annotation using the CSV file
results <- annotate_cell_types(
  input = "pbmc_markers.csv",
  tissue_name = "human PBMC",
  model = "claude-3-7-sonnet-20250219",
  api_key = Sys.getenv("ANTHROPIC_API_KEY")
)

Scenario 4: Custom Caching

For better control over caching behavior:

# Note: The annotate_cell_types function does not have built-in caching.
# If you need caching, you can implement it separately.

# Run annotation
results <- annotate_cell_types(
  input = pbmc_markers,
  tissue_name = "human PBMC",
  model = "claude-3-7-sonnet-20250219",
  api_key = Sys.getenv("ANTHROPIC_API_KEY"),
  top_gene_count = 10,
  debug = FALSE
)

# If you need custom caching, you can implement it using your own cache manager
# This is just a conceptual example and not part of the actual package
# cache_manager <- YourCacheManager$new(cache_dir = "path/to/cache")
# cache_manager$clear_cache()

Model Selection Guide

mLLMCelltype supports a wide range of LLM models. Here’s a guide to help you choose:

High Performance Models

For the most accurate annotations:

  • Anthropic Claude 3.7 Sonnet (claude-3-7-sonnet-20250219): Excellent biological knowledge, best for discussion
  • OpenAI GPT-4o (gpt-4o): Strong overall performance, good biological knowledge
  • Google Gemini 1.5 Pro (gemini-1.5-pro): Good performance with detailed reasoning

Balanced Performance/Cost Models

For good results with lower API costs:

  • Anthropic Claude 3.5 Sonnet (claude-3-5-sonnet-20240620): Good balance of performance and cost
  • X.AI Grok-3 (grok-3): Competitive performance at lower cost
  • DeepSeek V3 (deepseek-v3): Good performance for specialized tissues

Economy Models

For preliminary exploration or large datasets:

  • Qwen 2.5 (qwen-max-2025-01-25): Good performance for the cost
  • Zhipu GLM-4 (glm-4): Economical option with decent performance
  • MiniMax (minimax): Cost-effective for initial exploration

Free Models via OpenRouter

For users with limited API credits or budget constraints:

  • Meta Llama 4 Maverick (meta-llama/llama-4-maverick:free): Most reliable and fast, recommended for consensus checking
  • NVIDIA Nemotron Ultra 253B (nvidia/llama-3.1-nemotron-ultra-253b-v1:free): Good performance with consistent formatting
  • Microsoft MAI-DS-R1 (microsoft/mai-ds-r1:free): Reliable with good response time
  • DeepSeek Chat v3 (deepseek/deepseek-chat-v3-0324:free): Free model with 163K context window (may occasionally return empty results)

Based on our testing, we recommend the following free models:

  • meta-llama/llama-4-maverick:free: Most reliable and fast, recommended for consensus checking
  • nvidia/llama-3.1-nemotron-ultra-253b-v1:free: Good performance with consistent formatting
  • microsoft/mai-ds-r1:free: Reliable with good response time

Some models may have limitations:

  • deepseek/deepseek-chat-v3-0324:free: May occasionally return empty results
  • thudm/glm-z1-9b:free: May return localized error messages (“non-character parameter”) when used for consensus checking

These free models are accessed through OpenRouter and don’t consume credits, but may have limitations compared to paid models. Use the :free suffix in the model name to access them.

# Example of using a free model via OpenRouter
# First, set your OpenRouter API key
Sys.setenv(OPENROUTER_API_KEY = "your-openrouter-api-key")

# Then use a free model with the :free suffix
free_model_results <- annotate_cell_types(
  input = pbmc_markers,
  tissue_name = "human PBMC",
  model = "meta-llama/llama-4-maverick:free",  # Note the :free suffix
  api_key = Sys.getenv("OPENROUTER_API_KEY")
  # No need to specify provider - it's automatically detected from the model name format
)

Integration with Seurat Workflow

Here’s a complete example of integrating mLLMCelltype with a Seurat workflow:

library(Seurat)
library(mLLMCelltype)
library(ggplot2)

# Load data
data("pbmc_small")

# Standard Seurat preprocessing
pbmc_small <- NormalizeData(pbmc_small)
pbmc_small <- FindVariableFeatures(pbmc_small)
pbmc_small <- ScaleData(pbmc_small)
pbmc_small <- RunPCA(pbmc_small)
pbmc_small <- FindNeighbors(pbmc_small)
pbmc_small <- FindClusters(pbmc_small, resolution = 0.5)
pbmc_small <- RunUMAP(pbmc_small, dims = 1:10)

# Find markers for each cluster
pbmc_markers <- FindAllMarkers(pbmc_small, only.pos = TRUE, min.pct = 0.25, logfc.threshold = 0.25)

# Define models to use
models <- c(
  "claude-3-7-sonnet-20250219",
  "gpt-4o",
  "gemini-1.5-pro"
)

# API keys
api_keys <- list(
  anthropic = Sys.getenv("ANTHROPIC_API_KEY"),
  openai = Sys.getenv("OPENAI_API_KEY"),
  gemini = Sys.getenv("GEMINI_API_KEY")
)

# Run annotation with multiple models
results <- list()
for (model in models) {
  provider <- get_provider(model)
  api_key <- api_keys[[provider]]

  results[[model]] <- annotate_cell_types(
    input = pbmc_markers,
    tissue_name = "human PBMC",
    model = model,
    api_key = api_key,
    top_gene_count = 10
  )

  # Add individual model results to Seurat object
  column_name <- paste0("cell_type_", gsub("[^a-zA-Z0-9]", "_", model))
  pbmc_small[[column_name]] <- plyr::mapvalues(
    x = as.character(Idents(pbmc_small)),
    from = as.character(0:(length(results[[model]])-1)),
    to = results[[model]]
  )
}

# Create consensus
consensus_results <- interactive_consensus_annotation(
  input = pbmc_markers,
  tissue_name = "human PBMC",
  models = models,  # Use all the models defined above
  api_keys = api_keys,
  controversy_threshold = 0.7,
  entropy_threshold = 1.0,
  consensus_check_model = "claude-3-7-sonnet-20250219"
)

# Add consensus results to Seurat object
pbmc_small$cell_type_consensus <- plyr::mapvalues(
  x = as.character(Idents(pbmc_small)),
  from = as.character(0:(length(consensus_results$final_annotations)-1)),
  to = consensus_results$final_annotations
)

# Extract consensus metrics from the consensus results
consensus_metrics <- lapply(names(consensus_results$initial_results$consensus_results), function(cluster_id) {
  metrics <- consensus_results$initial_results$consensus_results[[cluster_id]]
  return(list(
    cluster = cluster_id,
    consensus_proportion = metrics$consensus_proportion,
    entropy = metrics$entropy
  ))
})

# Convert to data frame for easier handling
metrics_df <- do.call(rbind, lapply(consensus_metrics, data.frame))

# Add consensus proportion to Seurat object
pbmc_small$consensus_proportion <- as.numeric(plyr::mapvalues(
  x = as.character(Idents(pbmc_small)),
  from = metrics_df$cluster,
  to = metrics_df$consensus_proportion
))

# Add entropy to Seurat object
pbmc_small$shannon_entropy <- as.numeric(plyr::mapvalues(
  x = as.character(Idents(pbmc_small)),
  from = metrics_df$cluster,
  to = metrics_df$entropy
))

# Visualize results
p1 <- DimPlot(pbmc_small, group.by = "cell_type_consensus", label = TRUE, repel = TRUE) +
  ggtitle("Cell Type Annotations") +
  theme(plot.title = element_text(hjust = 0.5))

p2 <- FeaturePlot(pbmc_small, features = "consensus_proportion", cols = c("yellow", "green", "blue")) +
  ggtitle("Consensus Proportion") +
  theme(plot.title = element_text(hjust = 0.5))

p3 <- FeaturePlot(pbmc_small, features = "shannon_entropy", cols = c("red", "orange")) +
  ggtitle("Shannon Entropy") +
  theme(plot.title = element_text(hjust = 0.5))

# Combine plots
p1 | p2 | p3

Advanced Parameter Tuning

Adjusting top_gene_count

The top_gene_count parameter controls how many top marker genes per cluster are used for annotation:

# Using more genes (better for well-characterized tissues)
results_more_genes <- annotate_cell_types(
  input = pbmc_markers,
  tissue_name = "human PBMC",
  model = "claude-3-7-sonnet-20250219",
  api_key = Sys.getenv("ANTHROPIC_API_KEY"),
  top_gene_count = 20  # Using more genes
)

# Using fewer genes (better for noisy data)
results_fewer_genes <- annotate_cell_types(
  input = pbmc_markers,
  tissue_name = "human PBMC",
  model = "claude-3-7-sonnet-20250219",
  api_key = Sys.getenv("ANTHROPIC_API_KEY"),
  top_gene_count = 5   # Using fewer genes
)

Adjusting controversy_threshold

The controversy_threshold parameter in the interactive_consensus_annotation function controls which clusters are considered controversial and require discussion:

# Example of using interactive_consensus_annotation with different controversy thresholds
# Lower threshold (more clusters will be discussed)
consensus_results_low_threshold <- interactive_consensus_annotation(
  input = pbmc_markers,
  tissue_name = "human PBMC",
  models = c("claude-3-7-sonnet-20250219", "gpt-4o", "gemini-2.0-flash"),
  api_keys = list(
    "anthropic" = Sys.getenv("ANTHROPIC_API_KEY"),
    "openai" = Sys.getenv("OPENAI_API_KEY"),
    "gemini" = Sys.getenv("GEMINI_API_KEY")
  ),
  controversy_threshold = 0.3  # Lower threshold - more clusters will be discussed
)

# Higher threshold (fewer clusters will be discussed)
consensus_results_high_threshold <- interactive_consensus_annotation(
  input = pbmc_markers,
  tissue_name = "human PBMC",
  models = c("claude-3-7-sonnet-20250219", "gpt-4o", "gemini-2.0-flash"),
  api_keys = list(
    "anthropic" = Sys.getenv("ANTHROPIC_API_KEY"),
    "openai" = Sys.getenv("OPENAI_API_KEY"),
    "gemini" = Sys.getenv("GEMINI_API_KEY")
  ),
  controversy_threshold = 0.7  # Higher threshold - fewer clusters will be discussed
)

Performance Considerations

API Rate Limits and Costs

Different LLM providers have different rate limits and pricing:

  • OpenAI: Higher rate limits but can be more expensive
  • Anthropic: Good balance of rate limits and cost
  • Google: Competitive pricing with good rate limits
  • Others: Generally lower cost but may have stricter rate limits

To manage costs and rate limits:

  1. Use caching to avoid redundant API calls
  2. Start with a single model for exploration
  3. Use more economical models for initial testing
  4. Reserve multi-model consensus for final analysis

Execution Time

Typical execution times:

  • Single model annotation: 5-30 seconds per cluster
  • Multi-model consensus: 1-5 minutes for a typical dataset
  • Discussion process: Additional 1-3 minutes per controversial cluster

To improve performance:

  1. Use a smaller top_gene_count for faster execution
  2. Enable caching to reuse results
  3. Use a higher controversy_threshold to reduce the number of clusters that require discussion

Troubleshooting

Common Issues with OpenRouter

If you encounter “No auth credentials found” errors with OpenRouter:

  • Verify your API key is correct and active
  • Ensure you’re using the correct format for the model name (e.g., provider/model-name:free)
  • Try a different free model, as some models may have access restrictions

Non-English Model Error Messages

If you see “non-character parameter” (non-character parameter) errors:

  • This typically occurs when using non-English language models like thudm/glm-z1-9b:free for consensus checking
  • Switch to an English model like meta-llama/llama-4-maverick:free for consensus checking

Empty Results

If a model returns empty results:

  • Try increasing the timeout or retry with the same model
  • Some models like deepseek/deepseek-chat-v3-0324:free may occasionally return empty results
  • Switch to a more reliable model if the problem persists

Next Steps

Now that you understand the detailed usage of mLLMCelltype, you can explore: