biolm.pipeline package

Submodules

biolm.pipeline.async_executor module

Async batch execution utilities with semaphore-based rate limiting.

class biolm.pipeline.async_executor.AsyncBatchExecutor(max_concurrent: int = 10, batch_size: int | None = None, progress_desc: str = 'Processing', show_progress: bool = True)

Bases: object

Execute async tasks in batches with rate limiting.

Features: - Semaphore-based concurrency control - Progress tracking with tqdm - Error handling per item - Dynamic batch sizing

Args:

max_concurrent: Maximum number of concurrent tasks batch_size: Size of each batch (None for no batching) progress_desc: Description for progress bar show_progress: Whether to show progress bar

Example:
default
>>> executor = AsyncBatchExecutor(max_concurrent=10)
>>> results = await executor.execute(items, process_func)
async execute(items: list[T], func: Callable[[T], Coroutine[Any, Any, R]], return_exceptions: bool = False) list[R]

Execute async function on all items with rate limiting.

Args:

items: List of items to process func: Async function to apply to each item return_exceptions: If True, return exceptions instead of raising

Returns:

List of results (same order as input)

async execute_batched(items: list[T], batch_func: Callable[[list[T]], Coroutine[Any, Any, list[R]]], batch_size: int | None = None, return_exceptions: bool = False) list[R]

Execute async function on batches of items.

Args:

items: List of items to process batch_func: Async function that processes a batch and returns results batch_size: Size of each batch (uses self.batch_size if None) return_exceptions: If True, return exceptions instead of raising

Returns:

Flattened list of results

class biolm.pipeline.async_executor.CachingExecutor(executor: AsyncBatchExecutor, cache_check_func: Callable[[T], R | None] | None = None, cache_store_func: Callable[[T, R], None] | None = None)

Bases: object

Executor with built-in caching support.

Checks cache before execution and updates cache after completion.

Args:

executor: AsyncBatchExecutor instance cache_check_func: Function to check if result is cached (returns cached result or None) cache_store_func: Function to store result in cache

Example:
default
>>> def cache_check(item):
...     return datastore.get_prediction(item['sequence_id'], 'stability')
>>>
>>> def cache_store(item, result):
...     datastore.add_prediction(item['sequence_id'], 'stability', result)
>>>
>>> executor = CachingExecutor(
...     AsyncBatchExecutor(max_concurrent=10),
...     cache_check, cache_store
... )
>>> results = await executor.execute_with_cache(items, process_func)
async execute_with_cache(items: list[T], func: Callable[[T], Coroutine[Any, Any, R]], return_exceptions: bool = False) tuple[list[R], int, int]

Execute with caching.

Returns:

Tuple of (results, cached_count, computed_count)

class biolm.pipeline.async_executor.StreamingExecutor(executor: AsyncBatchExecutor, result_callback: Callable[[T, R], Coroutine[Any, Any, None]] | None = None)

Bases: object

Executor that streams results as they complete.

Useful for pipelines where downstream stages can start before all results are ready.

Args:

executor: AsyncBatchExecutor instance result_callback: Optional callback for each completed result

Example:
default
>>> async def on_result(item, result):
...     print(f"Completed: {item}")
...     downstream_queue.put(result)
>>>
>>> executor = StreamingExecutor(
...     AsyncBatchExecutor(max_concurrent=10),
...     result_callback=on_result
... )
>>> await executor.execute_streaming(items, process_func)
async execute_streaming(items: list[T], func: Callable[[T], Coroutine[Any, Any, R]], return_exceptions: bool = False) list[R]

Execute with streaming results.

Results are passed to callback as they complete.

Returns:

List of results (order may differ from input)

async biolm.pipeline.async_executor.process_dataframe_async(df: DataFrame, process_func: Callable[[Series], Coroutine[Any, Any, Any]], result_column: str, max_concurrent: int = 10, show_progress: bool = True, progress_desc: str = 'Processing') DataFrame

Process DataFrame rows asynchronously and add results as a new column.

Args:

df: Input DataFrame process_func: Async function to process each row (receives a Series) result_column: Name of column to store results max_concurrent: Maximum concurrent tasks show_progress: Whether to show progress bar progress_desc: Description for progress bar

Returns:

DataFrame with new result column

Example:
default
>>> async def predict_stability(row):
...     return await model.predict(row['sequence'])
>>>
>>> df = await process_dataframe_async(
...     df, predict_stability, 'stability_score'
... )
async biolm.pipeline.async_executor.process_sequences_batched(sequences: list[str], batch_func: Callable[[list[str]], Coroutine[Any, Any, list[Any]]], batch_size: int = 32, max_concurrent: int = 5, show_progress: bool = True, progress_desc: str = 'Processing sequences') list[Any]

Process sequences in batches.

Args:

sequences: List of sequences batch_func: Async function that processes a batch of sequences batch_size: Size of each batch max_concurrent: Maximum concurrent batches show_progress: Whether to show progress bar progress_desc: Description for progress bar

Returns:

List of results (one per sequence)

Example:
default
>>> async def predict_batch(seqs):
...     return await model.predict_batch(seqs)
>>>
>>> results = await process_sequences_batched(
...     sequences, predict_batch, batch_size=32
... )

biolm.pipeline.base module

Base Pipeline classes for stage management and execution.

class biolm.pipeline.base.BasePipeline(datastore: DuckDBDataStore | str | Path | None = None, run_id: str | None = None, output_dir: str | Path = 'pipeline_outputs', resume: bool = False, verbose: bool = True, input_schema: InputSchema | None = None)

Bases: ABC

Base class for all pipeline types.

Provides: - Stage management and dependency resolution - Async execution with progress tracking - Caching and resumability - Export and visualization

When no datastore is provided, the pipeline automatically creates a DuckDB cache under .biolm/pipelines//. The pipeline_id (and full cache path) is exposed via metadata so users can reconnect to the same cache in later sessions.

Args:

datastore: DataStore instance or path to a DuckDB file. Required. run_id: Unique run identifier (auto-generated if not provided). output_dir: Directory for CSV/Parquet exports (default pipeline_outputs). resume: Whether to resume from a previous run. verbose: Enable verbose output.

add_stage(stage: Stage)

Add a stage to the pipeline.

close()

Close the pipeline’s datastore connection.

Safe to call multiple times. Called automatically by __exit__ and __aexit__. Also called by __del__ for auto-created datastores only (user-provided datastores are not closed on GC so the caller can continue using them after the pipeline is discarded).

export_to_csv(output_path: str | Path | None = None)

Export final results to CSV.

classmethod from_db(db_path: str | Path, definition_id: str | None = None, run_id: str | None = None, verbose: bool = True) BasePipeline

Reconstruct a pipeline from an existing DuckDB database.

Useful for recovering after a kernel death without re-running already-completed stages.

Args:

db_path: Path to the DuckDB database file. definition_id: Specific definition to load (None = latest). run_id: Run ID for the reconstructed pipeline (None = generate new). verbose: Enable verbose output.

Returns:

Reconstructed BasePipeline subclass instance.

Example:

default
pipeline = DataPipeline.from_db("my_pipeline.duckdb")
pipeline.run(resume=True)
get_final_data() DataFrame

Get the final output DataFrame.

Materializes from the merged final WorkingSet via DuckDB. In a branched DAG the last-added stage is not necessarily the last executed sink, so we prefer _final_ws (set at end of run()).

The returned DataFrame always contains at minimum:

  • sequence_id, sequence, length, hash

  • source_label — label from the generation config; None when unset. Always present regardless of whether any labels were set.

  • One column per prediction type from upstream prediction stages.

property metadata: PipelineMetadata

Return metadata for reconnecting to this pipeline’s cache later.

query(sql: str, params=None) DataFrame

Execute arbitrary SQL against the pipeline’s DuckDB datastore.

results() DataFrame

Return the final output DataFrame. Alias for get_final_data().

The returned DataFrame always contains at minimum:

  • sequence_id, sequence, length, hash

  • source_label — the label set on the generation config (e.g. DirectGenerationConfig.label). None when no label was set. Always present — do not guard on "source_label" in df.columns.

  • One column per prediction type computed by any prediction stages.

run(enable_streaming: bool = True, **kwargs) dict[str, biolm.pipeline.base.StageResult]

Run the pipeline synchronously.

This is a convenience wrapper around run_async(). Works in both script/notebook environments (detects running event loops).

async run_async(enable_streaming: bool = True, **kwargs) dict[str, biolm.pipeline.base.StageResult]

Run the pipeline asynchronously.

Args:
enable_streaming: Stream prediction results through per-sequence

filters for better parallelism and lower latency (default True).

Returns:

Dict mapping stage names to StageResults

summary() DataFrame

Get pipeline summary statistics.

class biolm.pipeline.base.InputSchema(columns: list[str])

Bases: object

Describes the input columns for a pipeline.

When set, these columns are the primary data — sequence is not required. Columns are stored directly on the sequences table via ALTER TABLE ADD COLUMN so that materialize_working_set(), SQL filters, and item_columns all work via direct JOINs.

Hashing uses all columns (sorted alphabetically) joined with \x00 separators so that identical rows produce the same hash regardless of column order.

Args:

columns: List of column names that comprise the primary input.

columns: list[str]
hash_row(row: dict[str, str]) str

SHA-256 hash of the row values across all input columns (sorted).

class biolm.pipeline.base.PipelineContext(datastore: DuckDBDataStore, run_id: str)

Bases: object

Shared key-value store backed by DuckDB for inter-stage communication.

Stages can read/write arbitrary data through the context. Common use case: stage 1 predicts structures (stored in the structures table), stage 2 reads them for structure-conditioned generation.

Args:

datastore: The pipeline’s DuckDB datastore. run_id: Current pipeline run ID.

get(key: str, default: Any = None) Any

Retrieve a value from the pipeline context table.

get_structure(sequence_id: int, model_name: str | None = None) dict | None

Convenience: fetch a structure from the datastore’s structures table.

get_structures_for_ws(ws: WorkingSet, model_name: str | None = None) DataFrame

Fetch structures for all sequences in a WorkingSet.

set(key: str, value: Any)

Store a value in the pipeline context table.

class biolm.pipeline.base.PipelineMetadata(pipeline_id: str, cache_dir: Path, db_path: Path, run_id: str)

Bases: object

Metadata for a pipeline run — lets users retrieve and reuse cached results.

Attributes:

pipeline_id: Unique identifier for the pipeline’s cache directory. cache_dir: Path to the .biolm/pipelines/ cache directory. db_path: Path to the DuckDB database file inside the cache directory. run_id: The run ID for this execution (there can be multiple runs

sharing the same cache).

Example:

default
pipeline = DataPipeline(sequences=[...])
pipeline.run()
meta = pipeline.metadata
print(meta.pipeline_id)   # "20260302_143022_a1b2c3d4"
print(meta.cache_dir)     # ".biolm/pipelines/20260302_143022_a1b2c3d4"

# Later — reuse the same cache:
pipeline2 = DataPipeline(
    sequences=new_seqs,
    datastore=meta.db_path,   # or str(meta.cache_dir)
    resume=True,
)
cache_dir: Path
db_path: Path
pipeline_id: str
run_id: str
class biolm.pipeline.base.Stage(name: str, cache_key: str | None = None, depends_on: list[str] | None = None, model_name: str | None = None, max_concurrent: int = 10)

Bases: ABC

Abstract base class for pipeline stages.

A stage represents a single processing step in the pipeline. It can filter data, compute predictions, or transform sequences.

Args:

name: Stage name (must be unique within pipeline) cache_key: Unused collision-dedup key (auto-derived by PredictionStage) depends_on: List of stage names this stage depends on model_name: Model name for predictions/structures max_concurrent: Maximum concurrent API calls (for rate limiting)

Class Attributes:
merge_mode: How this stage’s output is merged when it runs in parallel

with other stages. "intersect" (default) = only keep sequences that pass all parallel stages (correct for filters). "union" = keep sequences that appear in any parallel stage output (correct for independent prediction stages that may skip sequences on error but should not drop others).

merge_mode: str = 'intersect'
async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]

Legacy DataFrame interface — used by streaming mode and GenerationStage.

Subclasses may override this for backward compatibility or for cases where a DataFrame is the natural input (e.g. generation with an empty df).

abstract async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]

Process data using WorkingSet (DuckDB-native).

All stages must implement this method. Stages that need actual sequence data (e.g. ClusteringStage) should call datastore.materialize_working_set(ws) internally — that is the stage’s responsibility, not the pipeline’s.

Args:

ws: Input WorkingSet (set of sequence IDs). datastore: DataStore for reading/writing data. **kwargs: Additional arguments (e.g. run_id).

Returns:

Tuple of (output WorkingSet, StageResult).

to_spec() dict

Return a serializable dict describing this stage.

Used by BasePipeline.run_async() to persist pipeline definitions to DuckDB (enabling DataPipeline.from_db() recovery after kernel death).

Subclasses must override this. Raises NotImplementedError by default.

class biolm.pipeline.base.StageResult(stage_name: str, input_count: int, output_count: int, filtered_count: int = 0, cached_count: int = 0, computed_count: int = 0, elapsed_time: float = 0.0, metadata: dict[str, typing.Any] = <factory>)

Bases: object

Result from a pipeline stage.

cached_count: int = 0
computed_count: int = 0
elapsed_time: float = 0.0
filtered_count: int = 0
input_count: int
metadata: dict[str, Any]
output_count: int
stage_name: str
class biolm.pipeline.base.WorkingSet(sequence_ids: frozenset[int])

Bases: object

Lightweight set of sequence IDs — replaces DataFrame as inter-stage transport.

Stages operate on DuckDB directly and pass only the set of surviving sequence IDs to the next stage. Materialization to DataFrame happens once at get_final_data() time.

Memory: 1M IDs ≈ 28 MB (frozenset[int]) vs 500 MB+ DataFrame.

difference(other: WorkingSet) WorkingSet

Return IDs in self but not in other.

classmethod from_ids(ids) WorkingSet

Create from any iterable of ints.

intersect(other: WorkingSet) WorkingSet

Return a new WorkingSet containing only IDs present in both sets.

sequence_ids: frozenset[int]
to_list() list[int]

Return sorted list (useful for DuckDB queries).

union(other: WorkingSet) WorkingSet

Return a new WorkingSet containing IDs from either set.

biolm.pipeline.clustering module

Sequence clustering and diversity analysis.

Provides tools for grouping similar sequences and measuring sequence space coverage.

Performance Notes: - For large datasets (>10k sequences), use sampling or embedding-based methods - Pairwise distance computations are O(n²) - use max_sample parameter - Embedding-based clustering scales much better than Hamming distance

class biolm.pipeline.clustering.ClusteringResult(cluster_ids: ndarray, centroids: list[str], centroid_indices: ndarray, n_clusters: int, silhouette_score: float | None = None, davies_bouldin_score: float | None = None, cluster_sizes: dict[int, int] | None = None)

Bases: object

Results from sequence clustering.

centroid_indices: ndarray
centroids: list[str]
cluster_ids: ndarray
cluster_sizes: dict[int, int] | None = None
davies_bouldin_score: float | None = None
n_clusters: int
silhouette_score: float | None = None
class biolm.pipeline.clustering.DiversityAnalyzer

Bases: object

Analyze sequence diversity and coverage.

Provides metrics for understanding sequence space exploration.

Performance Notes:
  • Shannon entropy: O(n*L) where L is sequence length

  • Pairwise distances: O(n²*L) - use max_sample for large n

  • All metrics scale linearly except pairwise distances

Example:
default
>>> analyzer = DiversityAnalyzer()
>>> metrics = analyzer.compute_all_metrics(sequences, max_sample=10000)
>>> print(f"Shannon entropy: {metrics['shannon_entropy']:.2f}")
classmethod compute_all_metrics(sequences: list[str], max_sample: int | None = 10000) dict[str, Union[float, dict]]

Compute all diversity metrics at once.

Args:

sequences: List of protein sequences max_sample: Unused, reserved for future embedding-based pairwise stats

Returns:

Dictionary with all diversity metrics

static motif_diversity(sequences: list[str], k: int = 3) dict[str, Union[int, float]]

Analyze k-mer (motif) diversity.

Args:

sequences: List of protein sequences k: Length of k-mers to analyze

Returns:

Dictionary with k-mer statistics

static shannon_entropy(sequences: list[str], normalize: bool = True) float

Calculate Shannon entropy of amino acid distribution.

Measures positional diversity across all sequences. Optimized for large datasets using vectorized operations.

Args:

sequences: List of protein sequences normalize: Normalize by log(20) for 0-1 range

Returns:

Shannon entropy (higher = more diverse)

exception biolm.pipeline.clustering.PerformanceWarning

Bases: UserWarning

Warning for potentially slow operations on large datasets.

class biolm.pipeline.clustering.SequenceClusterer(method: Literal['kmeans', 'dbscan', 'hierarchical'] = 'kmeans', n_clusters: int | None = None, similarity_metric: Literal['embedding'] = 'embedding', eps: float = 0.5, min_samples: int = 5, random_state: int = 42, mini_batch: bool = False, max_sample: int | None = None)

Bases: object

Cluster sequences by similarity.

Supports multiple clustering algorithms and similarity metrics.

Performance Notes:
  • For >10k sequences with Hamming distance, consider using max_sample

  • Embedding-based clustering scales much better (O(n) with MiniBatch K-means)

  • Use mini_batch=True for very large datasets (>50k sequences)

Args:

method: Clustering algorithm (‘kmeans’, ‘dbscan’, ‘hierarchical’) n_clusters: Number of clusters (for kmeans/hierarchical) similarity_metric: How to measure sequence similarity eps: DBSCAN epsilon parameter min_samples: DBSCAN minimum samples per cluster mini_batch: Use MiniBatchKMeans for large datasets (faster, approximate) max_sample: Maximum sequences to use for distance matrix (None = all)

Example:
default
>>> # For large datasets, use sampling or mini-batch
>>> clusterer = SequenceClusterer(
...     method='kmeans',
...     n_clusters=100,
...     mini_batch=True  # Much faster for large N
... )
>>> result = clusterer.cluster(sequences)
cluster(sequences: list[str], embeddings: ndarray | None = None) ClusteringResult

Cluster sequences and return assignments.

Args:

sequences: List of protein sequences embeddings: Pre-computed embeddings (if using embedding metric)

Returns:

ClusteringResult with cluster assignments and metrics

biolm.pipeline.clustering.analyze_diversity(sequences: list[str], max_sample: int | None = 10000) dict

Convenience function for analyzing sequence diversity.

Args:

sequences: List of protein sequences max_sample: Reserved for future embedding-based pairwise stats

Returns:

Dictionary of diversity metrics (shannon_entropy, motif diversity, uniqueness)

Example:
default
>>> metrics = analyze_diversity(sequences)
>>> print(f"Entropy: {metrics['shannon_entropy']:.2f}")
biolm.pipeline.clustering.cluster_sequences(sequences: list[str], method: str = 'kmeans', n_clusters: int = 10, embeddings: ndarray | None = None, mini_batch: bool = False, max_sample: int | None = None, **kwargs) ClusteringResult

Convenience function for clustering sequences using embeddings.

Performance Tips:
  • For >50k sequences, use mini_batch=True

  • Requires pre-computed embeddings (similarity_metric=’embedding’)

Args:

sequences: List of protein sequences method: Clustering algorithm n_clusters: Number of clusters embeddings: Pre-computed embeddings (required) mini_batch: Use MiniBatchKMeans for faster (approximate) clustering max_sample: Reserved; not yet used **kwargs: Additional arguments for SequenceClusterer

Returns:

ClusteringResult

biolm.pipeline.data module

Data-driven pipeline implementations.

DataPipeline: Load sequences from files/lists and run predictions SingleStepPipeline: Simplified single-step prediction pipeline

class biolm.pipeline.data.ClusteringStage(name: str, method: str = 'kmeans', n_clusters: int | None = None, similarity_metric: str = 'hamming', embedding_model: str | None = None, max_sample: int | None = None, **kwargs)

Bases: Stage

Sequence clustering stage.

Args:

name: Stage name method: Clustering algorithm (‘kmeans’, ‘dbscan’, ‘hierarchical’) n_clusters: Number of clusters (for kmeans/hierarchical) similarity_metric: How to measure similarity (‘hamming’, ‘embedding’) embedding_model: Model to use for embeddings (if similarity_metric=’embedding’)

async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]

Cluster sequences, add cluster columns, and return the enriched DataFrame.

async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]

Cluster sequences — materializes internally for scikit-learn.

Clustering doesn’t filter rows (all sequences survive), so the returned WorkingSet is the same as the input. Cluster assignments are stored as predictions (prediction_type 'cluster_id') so that materialize_working_set() includes them in get_final_data().

to_spec() dict

Return a serializable dict for pipeline definition persistence.

class biolm.pipeline.data.CofoldingPredictionStage(name: str, model_name: str, action: str = 'predict', prediction_type: str = 'structure', sequence_chain_id: str = 'A', sequence_entity_type: str = 'protein', static_entities=None, params: dict | None = None, batch_size: int = 1, item_columns: dict[str, str] | None = None, **kwargs)

Bases: Stage

Prediction stage for co-folding models (Boltz2, Chai-1).

Each sequence in the pipeline DataFrame is used as the primary entity (chain) in a multi-molecule folding request. Additional static entities — ligands, cofactors, DNA/RNA, or other protein chains — are injected via static_entities and held constant for every sequence.

The caller is responsible for providing the correct molecule field names and params for the target model. Boltz2 and Chai-1 both expect an item shaped like {'molecules': [{'id': ..., 'type': ..., 'sequence': ...}, ...]}.

Args:

name: Stage name. model_name: BioLM model slug ('boltz2', 'chai1'). action: API action (default 'predict'). prediction_type: Column name written for the confidence score

(default 'structure').

sequence_chain_id: Chain ID / name assigned to the pipeline’s primary

sequence in the molecules list.

sequence_entity_type: Molecule type for the primary sequence

('protein', 'dna', 'rna').

static_entities: List of FoldingEntity

objects appended to every molecules list after the primary chain.

params: Model-specific params dict passed directly to the API

(e.g. {'recycling_steps': 3, 'sampling_steps': 20} for Boltz).

batch_size: Sequences per API call (default 1; co-folding models are

typically limited to batch size 1).

Example:

default
from biolm.pipeline import FoldingEntity

pipeline.add_cofolding_prediction(
    model_name='boltz2',
    static_entities=[
        FoldingEntity(id='L', entity_type='ligand', smiles='c1ccccc1'),
    ],
    params={'recycling_steps': 3, 'sampling_steps': 20},
    depends_on=['filter_top50'],
)
merge_mode: str = 'union'
async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]

Run co-folding prediction for each sequence in df.

async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]

Run co-folding prediction using WorkingSet — no DataFrame transport.

to_spec() dict

Serialize to a dict for pipeline definition persistence.

Note: static_entities (FoldingEntity objects) are not serializable and are omitted. A reconstructed pipeline will not have static_entities and must have them re-attached manually after from_db().

class biolm.pipeline.data.DataPipeline(sequences: list[str] | DataFrame | str | Path = None, diff_mode: bool = False, input_columns: list[str] | None = None, **kwargs)

Bases: BasePipeline

Pipeline for processing existing sequences from files or lists.

Load sequences from CSV/FASTA/lists and run predictions/filtering.

Args:

sequences: Input sequences (list of strings, DataFrame, or file path) datastore: DataStore instance or path run_id: Unique run ID output_dir: Output directory resume: Resume from previous run verbose: Enable verbose output diff_mode: If True, merge new sequences with existing cached results.

Only computes predictions for uncached sequences. Use get_merged_results() or query_results() to efficiently access combined data without loading millions of rows into memory (SQL-based queries).

Example:
default
>>> # Standard mode
>>> pipeline = DataPipeline(sequences='sequences.csv')
>>> pipeline.add_prediction('esmfold', extractions='mean_plddt', columns='plddt')
>>> pipeline.add_filter(ThresholdFilter('plddt', min_value=70))
>>> results = pipeline.run()
default
>>> # Diff mode - add new sequences to existing pipeline (SQL-based, efficient)
>>> pipeline = DataPipeline(sequences='new_sequences.csv', diff_mode=True)
>>> pipeline.add_prediction('esmfold', extractions='mean_plddt', columns='plddt')
>>> results = pipeline.run()
>>> # Efficiently query specific data (doesn't load all millions of rows!)
>>> high_quality = pipeline.query_results("s.length > 100 AND p.value > 70")
>>> # Or get merged results with filters
>>> merged = pipeline.get_merged_results(prediction_types=['plddt', 'tm'])
add_clustering(method: str = 'kmeans', n_clusters: int | None = None, similarity_metric: str = 'hamming', embedding_model: str | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)

Add a sequence clustering stage.

Clusters sequences by similarity and adds cluster_id column to DataFrame.

Args:

method: Clustering algorithm (‘kmeans’, ‘dbscan’, ‘hierarchical’) n_clusters: Number of clusters (required for kmeans/hierarchical) similarity_metric: ‘hamming’ or ‘embedding’ embedding_model: Model name if using embedding similarity stage_name: Optional custom stage name depends_on: Optional list of stage names this stage depends on **kwargs: Additional arguments for clustering (eps, min_samples, etc.)

Example:
default
>>> # Cluster by sequence similarity
>>> pipeline.add_clustering(method='kmeans', n_clusters=10)
>>>
>>> # Cluster by embeddings
>>> pipeline.add_prediction('esm2-650m', action='encode', stage_name='embed')
>>> pipeline.add_clustering(
...     method='kmeans',
...     n_clusters=5,
...     similarity_metric='embedding',
...     embedding_model='esm2-650m',
...     depends_on=['embed']
... )
add_cofolding_prediction(model_name: str, action: str = 'predict', stage_name: str | None = None, prediction_type: str = 'structure', sequence_chain_id: str = 'A', sequence_entity_type: str = 'protein', static_entities=None, depends_on: list[str] | None = None, params: dict | None = None, batch_size: int = 1)

Add a co-folding prediction stage (Boltz2, Chai-1).

Each pipeline sequence becomes the primary entity in a multi-molecule folding request. static_entities injects ligands, cofactors, DNA/RNA strands, or additional protein chains that are constant across all designs.

The caller is responsible for providing the correct molecule field names via static_entities and the right params for the model.

Args:

model_name: BioLM model slug ('boltz2', 'chai1'). action: API action (default 'predict'). stage_name: Optional stage name (defaults to model_name). prediction_type: Column name for the confidence score. sequence_chain_id: Chain ID assigned to the primary sequence

(e.g. 'A' for Boltz, molecule name for Chai-1).

sequence_entity_type: Entity type for the primary sequence

('protein', 'dna', 'rna').

static_entities: List of FoldingEntity objects to include

in every request alongside the primary sequence.

depends_on: Upstream stage names. params: Model-specific params (e.g. {'recycling_steps': 3}). batch_size: Sequences per API call (default 1).

Example:

default
from biolm.pipeline import FoldingEntity

pipeline.add_cofolding_prediction(
    model_name='boltz2',
    static_entities=[
        FoldingEntity(id='L', entity_type='ligand', smiles='c1ccccc1'),
    ],
    params={'recycling_steps': 3, 'sampling_steps': 20},
    depends_on=['filter_top50'],
)
add_filter(filter_func: BaseFilter | Callable[[...], Any], stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)

Add a filter stage.

Args:

filter_func: Filter function or BaseFilter instance stage_name: Custom stage name depends_on: List of stage names this depends on. When None

(default), auto-depends on the last added stage. Pass depends_on=[] for earliest-level execution.

add_prediction(model_name: str, action: str = 'predict', extractions: str | list[Union[str, biolm.pipeline.data.ExtractionSpec]] | None = None, columns: str | dict[str, str] | None = None, params: dict | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)

Add a prediction stage.

Args:

model_name: BioLM model name action: API action (‘predict’, ‘encode’, ‘score’) extractions: API response key(s) to extract. Required for

predict/score actions. Can be a string for a single key or a list of strings / ExtractionSpec objects.

columns: Output column name(s). A string renames a single

extraction; a dict maps response keys to column names (unmapped keys keep their name).

params: Optional API parameters stage_name: Custom stage name (defaults to predict_{first_column}) depends_on: List of stage names this depends on. When None

(default), the stage auto-depends on the last added stage, creating a sequential chain. Pass depends_on=[] to run at the earliest possible level (parallel with other level-0 stages).

Example:

default
pipeline.add_prediction(
    "temberture-regression",
    extractions="prediction",
    columns="tm",
)
add_predictions(models: list[Union[str, dict]], action: str = 'predict', depends_on: list[str] | None = None, **kwargs)

Add multiple prediction stages at the same level (run in parallel).

Args:

models: List of model names or dicts with model configs action: Default action if not specified in dict depends_on: List of stage names all these depend on **kwargs: Default kwargs for all stages

Returns:

self for chaining

Example:
default
>>> pipeline.add_predictions([
...     {'model_name': 'temberture-regression', 'extractions': 'prediction', 'columns': 'tm'},
...     {'model_name': 'biolmsol', 'extractions': 'solubility_score', 'columns': 'solubility'},
... ])
add_structure_prediction(model_name: str, structure_key: str = 'pdb', extractions: str | list[Union[str, biolm.pipeline.data.ExtractionSpec]] | None = None, columns: str | dict[str, str] | None = None, plddt_key: str | None = None, structure_format: str | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)

Add a prediction stage that stores the structure from the response.

Convenience wrapper around add_prediction() with a StructureSpec.

Args:

model_name: BioLM model name (e.g. ‘esmfold’, ‘alphafold2’). structure_key: Response key containing the structure string. extractions: Optional scalar extractions (e.g. ‘mean_plddt’). columns: Output column name(s) for scalar extractions. plddt_key: Optional response key for confidence score. structure_format: ‘pdb’ or ‘cif’ (auto-detected from key if None). stage_name: Custom stage name. depends_on: Stage dependencies.

Example:

default
pipeline.add_structure_prediction(
    "esmfold",
    extractions="mean_plddt", columns="plddt",
    plddt_key="mean_plddt",
)
explore() dict[str, Any]

Return summary stats for the pipeline’s datastore (all via SQL).

Returns:

Dict with keys: sequences, embeddings, generated, completed_stages, predictions (dict of prediction_type → count).

get_merged_results(prediction_types: list[str] | None = None, sequence_filter: str | None = None) DataFrame

Get results merged with existing cached data (for diff mode).

This method is SQL-based and efficient - it doesn’t load millions of rows. Instead, it queries only the data you need using DuckDB’s columnar engine.

NOTE (Bug #5): sequence_filter and sql_where in query_results() are interpolated directly into SQL. These parameters are intended for internal/ trusted caller use only — never pass untrusted user input to them.

Args:

prediction_types: List of prediction types to include (None = all) sequence_filter: SQL WHERE clause to filter sequences (e.g., “length > 50”).

TRUSTED CALLERS ONLY — not safe for untrusted user input.

Returns:

DataFrame with requested sequences and predictions

Example:
default
>>> # Get all results (efficient - DuckDB only loads what's needed)
>>> df = pipeline.get_merged_results()
default
>>> # Get only specific predictions (columnar - even faster!)
>>> df = pipeline.get_merged_results(prediction_types=['tm', 'plddt'])
default
>>> # Get sequences matching criteria (predicate pushdown!)
>>> df = pipeline.get_merged_results(sequence_filter="length > 100")
plot(kind: str = 'funnel', **kwargs)

Convenience wrapper around PipelinePlotter.

Args:
kind: One of ‘funnel’, ‘predictions’, ‘distributions’, ‘scatter’,

‘correlation’, ‘diversity’, ‘temperature’.

**kwargs: Forwarded to the underlying plotter method.

scatter requires x_col and y_col. diversity accepts reference_sequence. temperature requires metric_col.

query(sql: str, params=None) DataFrame

Execute arbitrary SQL against the pipeline’s DuckDB datastore.

Args:

sql: DuckDB SQL query string. params: Optional list of query parameters.

Returns:

DataFrame with results.

Example:
default
>>> pipeline.query("SELECT * FROM sequences WHERE length > 100")
query_results(sql_where: str, columns: list[str] | None = None) DataFrame

Query results using SQL WHERE clause (for diff mode with large datasets).

This leverages DuckDB’s vectorized engine for maximum performance.

NOTE (Bug #5): sql_where is interpolated directly into SQL. TRUSTED CALLERS ONLY — never pass untrusted user input to this parameter.

Args:
sql_where: SQL WHERE clause using table aliases:
  • s.* for sequences table (e.g., “s.length > 100”)

  • Column names directly (no p. prefix needed)

TRUSTED CALLERS ONLY — not safe for untrusted user input.

columns: Columns to include (None = all available)

Returns:

DataFrame with matching sequences (only loads what matches!)

Example:
default
>>> # Find long sequences (columnar scan - fast!)
>>> df = pipeline.query_results("s.length > 200")
default
>>> # Complex filter with predictions
>>> df = pipeline.query_results(
...     "s.length > 100",
...     columns=['tm', 'plddt']
... )
stats(stage_name: str | None = None) DataFrame

Return per-stage counts from stage_completions.

Args:

stage_name: If provided, filter to that stage only.

Returns:

DataFrame with columns: stage_name, status, input_count, output_count, completed_at.

biolm.pipeline.data.Embed(model_name: str, sequences: list[str] | DataFrame | str | Path, layer: int | None = None, key: str | None = None, **kwargs) DataFrame

Convenience function for generating embeddings.

Args:
model_name: BioLM model name (e.g., 'esm2-8m', 'esm2-650m',

'ablang2'). See biolm.list_models() for the full slugged list — the family name alone ('esm2') is not a valid endpoint.

sequences: Input sequences layer: Optional layer number key: Response dict key containing embeddings. Auto-detected if None:

ESM2 models use “embeddings”, AbLang2 uses “seqcoding”, others default to “embedding”.

**kwargs: Additional arguments

Returns:

DataFrame with ‘sequence’, ‘sequence_id’, and ‘embedding’ columns

Example:
default
>>> df = Embed('esm2-8m', sequences=['MKTAYIAKQRQ', 'MKLAVID'])
class biolm.pipeline.data.EmbeddingSpec(key: str, layer: int | None = None, reduction: str | None = None)

Bases: object

Declarative specification for extracting embeddings from API responses.

Covers common response formats without writing a custom function.

Args:
key: Response dict key containing the embedding data (e.g.

"embedding", "seqcoding", "embeddings").

layer: Which layer to extract when the response contains multiple

layers (list of {layer: int, embedding: [...]} dicts). None stores all layers; an int stores only that layer.

reduction: Reduce per-token 2-D embeddings to a single vector:

"mean", "first", "last", "sum". None stores the full array as-is.

Examples:

default
# ablang2 returns {"seqcoding": [float, ...]}
EmbeddingSpec(key="seqcoding")

# esm2-8m returns {"embeddings": [{embedding: [...], layer: 33}]}
# Store only layer 33:
EmbeddingSpec(key="embeddings", layer=33)

# Per-residue → mean-pool:
EmbeddingSpec(key="embedding", reduction="mean")
key: str
layer: int | None = None
reduction: str | None = None
class biolm.pipeline.data.ExtractionSpec(response_key: str, reduction: str | None = None)

Bases: object

Specification for extracting a value from an API response (with reduction).

Use when you need to apply a reduction (mean, max, min, sum) to an array-valued response key. For simple scalar extractions, pass a plain string to extractions instead.

Args:

response_key: Key in API response dict, e.g. “plddt” reduction: Optional reduction for array values: “mean”, “max”, “min”, “sum”

reduction: str | None = None
response_key: str
class biolm.pipeline.data.FilterStage(name: str, filter_func: BaseFilter | Callable[[...], Any], **kwargs)

Bases: Stage

Generic filtering stage.

Args:

name: Stage name filter_func: Filter function or BaseFilter instance

async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]

Apply filter to DataFrame and return the filtered result.

async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]

Apply filter using WorkingSet.

Two execution paths — chosen at call time, not as a “fallback”:

  1. SQL-native (zero materialization): filters that implement to_sql() return a complete SELECT scoped to the working set. DuckDB executes it directly; no DataFrame is ever created.

  2. DataFrame-based: filters that cannot be expressed in SQL (e.g. HammingDistanceFilter, CustomFilter) must materialize a DataFrame. This is the correct path for those filters, not a fallback.

to_spec() dict

Return a serializable dict for pipeline definition persistence.

class biolm.pipeline.data.MatrixExtractionSpec(prefix: str = 'ddg', values_key: str = 'ddG_matrix.values', row_labels_key: str = 'ddG_matrix.residue_axis', col_labels_key: str = 'ddG_matrix.amino_acid_axis', mutation_key: str | None = None, value_key: str | None = None)

Bases: object

Flattens a per-mutation response into individual prediction rows.

Each mutation becomes a separate prediction row with prediction_type formatted as ‘{prefix}_{label}’ (e.g., ‘ddg_M1A’).

Two modes:
  • Matrix mode (SPURS): 2D array + row/col labels.

  • List mode (ThermoMPNN): list of dicts with mutation name + value.

Args:

prefix: Prediction type prefix (e.g. “ddg”). values_key: Dot-path to 2D array in response (matrix mode). row_labels_key: Dot-path to row labels (position labels). col_labels_key: Dot-path to column labels (amino acid labels). mutation_key: Dict key for mutation name (list mode). If set, uses list mode. value_key: Dict key for the numeric value (list mode).

col_labels_key: str = 'ddG_matrix.amino_acid_axis'
mutation_key: str | None = None
prefix: str = 'ddg'
row_labels_key: str = 'ddG_matrix.residue_axis'
value_key: str | None = None
values_key: str = 'ddG_matrix.values'
exception biolm.pipeline.data.PipelineAPIAuthError(status_code: int, payload: Any, model_name: str)

Bases: RuntimeError

Raised when the BioLM API returns 401/402 (auth/billing) during a stage.

These errors are not retriable per-item — every batch will hit the same failure — so the pipeline fails fast instead of producing a silent empty result. skip_on_error=True does NOT swallow this; callers always see an unambiguous failure with the upstream error payload.

biolm.pipeline.data.Predict(model_name: str, sequences: list[str] | DataFrame | str | Path, extractions: str | list | None = None, params: dict | None = None, **kwargs) DataFrame

Convenience function for single-step prediction.

Args:

model_name: BioLM model name sequences: Input sequences extractions: API response key(s) to extract. Required — pass the response

key for your model (e.g. extractions='prediction' for temberture, extractions='mean_plddt' for esmfold). Use a list or ExtractionSpec for multiple extractions.

params: Optional API parameters **kwargs: Additional arguments

Returns:

DataFrame with predictions

Example:
default
>>> df = Predict('temberture-regression', sequences=['MKTAYIAKQRQ'], extractions='prediction')
class biolm.pipeline.data.PredictionStage(name: str, model_name: str, action: str = 'predict', params: dict | None = None, batch_size: int = 32, max_concurrent: int = 5, max_connections: int = 10, item_columns: dict[str, str] | None = None, extractions: str | list[Union[str, biolm.pipeline.data.ExtractionSpec]] | None = None, columns: str | dict[str, str] | None = None, embedding_extractor: EmbeddingSpec | Callable[[dict], Any] | None = None, structure_output: StructureSpec | None = None, structure_input: dict[str, str] | None = None, matrix_extraction: MatrixExtractionSpec | None = None, **kwargs)

Bases: Stage

Generic prediction stage using BioLM API.

Uses merge_mode = "union" so that when multiple prediction stages run in parallel, sequences processed by any stage survive the merge — i.e., independent predictions do not cancel each other out.

Args:

name: Stage name model_name: BioLM model name (e.g., ‘esmfold’, ‘esm2’, ‘temberture-regression’) action: API action (‘predict’, ‘encode’, ‘score’) prediction_type: Type of prediction for caching (e.g., ‘structure’, ‘stability’, ‘embedding’) params: Optional parameters for the API call batch_size: Number of sequences per pipeline batch (default 32).

Each pipeline batch becomes one SDK call, which the SDK may further split by the model’s maxItems schema limit.

max_concurrent: Maximum pipeline batches in flight at once (default 5).

Controls how many batches are dispatched concurrently at the pipeline level. Higher values keep the API more saturated but use more memory (up to max_concurrent * batch_size items plus their responses in memory at once).

max_connections: Maximum concurrent HTTP connections to the API

(default 10). This is the SDK-level semaphore that throttles the actual HTTP requests. Each pipeline batch may be split into multiple sub-requests by the model’s maxItems limit; max_connections caps how many of those sub-requests run simultaneously across all in-flight batches.

structure_output: Store the structure from this model’s response. structure_input: Inject structures from upstream models into API items.

Maps API field name → source model name (e.g. {"pdb": "esmfold"}).

matrix_extraction: Flatten a per-mutation response into individual

prediction rows (e.g. for DMS heatmaps).

merge_mode: str = 'union'
async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]

Process sequences through prediction model (legacy DataFrame path).

Uses the same bounded-concurrency batching as process_ws — see that method’s docstring for the design rationale.

Performance: single DuckDB anti-join for cache detection, bounded- concurrency API dispatch, single JOIN query to merge predictions back.

async process_streaming(df: DataFrame, datastore: DuckDBDataStore, **kwargs)

Process sequences and yield results as batches complete (streaming).

Yields DataFrames as API batches complete instead of waiting for all results. This allows downstream stages to start processing immediately.

async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]

Process sequences using WorkingSet with bounded-concurrency batching.

Batching strategy (two levels):

This method splits uncached sequences into pipeline-level batches of batch_size items (default 32) and keeps up to max_concurrent batches in flight at once. As each batch completes, its results are written to DuckDB immediately and a new batch is dispatched.

Each pipeline batch is itself an SDK call that may be further split by the model’s maxItems schema limit — the SDK handles that internally via asyncio.gather throttled by a semaphore.

Why bounded concurrency instead of all-at-once or sequential:

  • Sequential (old behavior) leaves the API idle between batches. With 300 sequences and 200ms API latency, that’s 10 idle round-trips.

  • All-at-once (process_streaming style) has no backpressure — 10k sequences creates 300+ in-flight tasks with all items and response payloads in memory simultaneously.

  • Bounded concurrency keeps the API saturated (max_concurrent batches in flight) while bounding memory to at most max_concurrent * batch_size items plus their results. DuckDB writes happen as each batch lands, providing natural backpressure: a slow datastore slows down new batch dispatch.

Steps:
  1. Cache check via anti-join (DuckDB)

  2. Fetch only uncached (sequence_id, sequence) pairs

  3. Dispatch batches with bounded concurrency

  4. Write results to DuckDB as each batch completes

  5. Return WorkingSet of IDs that have predictions

to_spec() dict

Return a serializable dict for pipeline definition persistence.

class biolm.pipeline.data.SingleStepPipeline(model_name: str, action: str = 'predict', sequences: list[str] | DataFrame | str | Path = None, params: dict | None = None, extractions=None, columns=None, embedding_extractor=None, **kwargs)

Bases: DataPipeline

Simplified pipeline for single-step predictions.

Convenience class for running a single prediction model on sequences.

Args:

model_name: BioLM model name action: API action (‘predict’, ‘encode’, ‘score’) sequences: Input sequences params: Optional API parameters **kwargs: Additional arguments passed to DataPipeline

Example:
default
>>> pipeline = SingleStepPipeline(
...     model_name='esmfold',
...     sequences=['MKTAYIAKQRQ', 'MKLAVID']
... )
>>> results = pipeline.run()
>>> df = pipeline.get_final_data()
class biolm.pipeline.data.StructureSpec(key: str, format: str | None = None, plddt_key: str | None = None, index: int | None = 0)

Bases: object

Specification for extracting and storing a structure from an API response.

Args:

key: Response dict key containing the structure string (e.g. “pdb”, “cif”, “pdbs”). format: Structure format — “pdb” or “cif”. Auto-detected from key if None. plddt_key: Optional response key for a confidence score to store alongside. index: For list-valued keys (e.g. AF2 “pdbs”), which element to store (default 0).

detect_format() str
format: str | None = None
index: int | None = 0
key: str
plddt_key: str | None = None

biolm.pipeline.datastore module

Backward-compatibility shim.

Old code imported from biolm.pipeline.datastore; the implementation has moved to biolm.pipeline.datastore_duckdb. Import DataStore from either location — they resolve to the same class.

biolm.pipeline.datastore.DataStore

alias of DuckDBDataStore

biolm.pipeline.datastore_duckdb module

DuckDB-based DataStore with Parquet backend for efficient large-scale data management.

Key features: - Columnar storage (Parquet) - Out-of-core queries (bigger than RAM) - Vectorized anti-join deduplication - 5-50× faster than pandas for aggregations - Diff-mode friendly batch inserts

class biolm.pipeline.datastore_duckdb.DuckDBDataStore(db_path: str | Path | None = None, data_dir: str | Path | None = None)

Bases: object

DuckDB + Parquet based datastore for efficient sequence management.

Optimized for: - Millions of sequences - Complex queries (joins, filters, aggregations) - Out-of-core operations (bigger than RAM) - Diff mode with efficient deduplication

Args:

db_path: Path to DuckDB database file data_dir: Directory for Parquet files and large data

Example:
default
>>> ds = DuckDBDataStore("pipeline.db", "data/")
>>> seq_id = ds.add_sequence("MKLLIV")
>>> ds.add_prediction(seq_id, "tm", "temberture-regression", 65.5)
>>>
>>> # Efficient query - no memory explosion
>>> high_tm = ds.query("SELECT * FROM predictions WHERE value > 60")
add_embedding(sequence_id: int, model_name: str, embedding: ndarray, layer: int | None = None)

Add embedding stored inline in DuckDB as FLOAT[] (no per-file Parquet overhead).

Args:

sequence_id: Sequence ID model_name: Model name embedding: Numpy array layer: Optional layer number

add_embeddings_batch(data: list[dict])

Batch-insert embeddings in a single DuckDB statement.

Each dict must have: sequence_id (int), model_name (str), embedding (np.ndarray). Optional: layer (int or None).

Significantly faster than N individual add_embedding() calls — one INSERT…SELECT vs. N individual row inserts.

add_generation_metadata(sequence_id: int, model_name: str, run_id: str = '', temperature: float | None = None, top_k: int | None = None, top_p: float | None = None, num_return_sequences: int | None = None, do_sample: bool | None = None, repetition_penalty: float | None = None, max_length: int | None = None, sampling_params: dict | None = None) int

Store generation parameters for a sequence.

Returns:

metadata_id of the inserted row.

add_generation_metadata_batch(rows: list[dict]) None

Batch-insert generation metadata — one DuckDB round-trip.

Each dict in rows must have at minimum sequence_id and model_name. Optional fields: temperature, top_k, top_p, num_return_sequences, do_sample, repetition_penalty, max_length.

add_prediction(sequence_id: int, prediction_type: str, model_name: str, value: float | None, metadata: dict | None = None)

Add single prediction (convenience wrapper).

add_prediction_by_sequence(sequence: str, prediction_type: str, model_name: str, value: float | None, metadata: dict | None = None) int

Add a prediction, creating the sequence if it doesn’t exist.

Returns:

prediction_id of the inserted row.

add_predictions_batch(data: list[dict[str, Any]])

Batch add predictions efficiently.

Args:
data: List of dicts with keys: sequence_id, prediction_type,

model_name, value, metadata (optional)

Example:
default
>>> ds.add_predictions_batch([
...     {'sequence_id': 1, 'prediction_type': 'tm',
...      'model_name': 'temberture-regression', 'value': 65.5},
...     {'sequence_id': 2, 'prediction_type': 'tm',
...      'model_name': 'temberture-regression', 'value': 70.2},
... ])
add_sequence(sequence: str) int

Add single sequence (convenience wrapper).

add_sequences_batch(sequences: list[str] | None = None, deduplicate: bool = True, input_df: DataFrame | None = None, input_columns: list[str] | None = None) list[int]

Add multiple sequences efficiently using anti-join deduplication.

This is the RECOMMENDED way to add sequences - vectorized and fast!

Two calling conventions:

  1. Legacy (sequence-only): add_sequences_batch(["MKLLIV", ...])

  2. Multi-column (arbitrary input columns): add_sequences_batch(input_df=df, input_columns=["heavy_chain", "light_chain"]) In this mode, the hash is computed across all input columns, and the column values are stored directly on the sequences table. A sequence column is still written (concatenation of all input columns joined with :) so downstream code has a fallback.

Args:

sequences: List of sequence strings (legacy path). deduplicate: Use anti-join to skip existing sequences. input_df: DataFrame with input columns (multi-column path). input_columns: Column names in input_df to use as primary data.

Returns:

List of sequence_ids (new and existing), preserving input order.

add_structure(sequence_id: int, model_name: str, structure_str: str | None = None, format: str = 'pdb', plddt_mean: float | None = None, plddt: float | None = None) int

Store a structure gzip-compressed as BLOB (~8-12x smaller than plain TEXT).

Args:

sequence_id: Sequence ID. model_name: Model that produced the structure (e.g. ‘esmfold’). structure_str: Full structure file content as a string. format: ‘pdb’ or ‘cif’ (default ‘pdb’). plddt_mean: Mean pLDDT score (optional). plddt: Alias for plddt_mean.

Returns:

structure_id of the inserted row.

close()

Close the DuckDB connection and release the file lock.

Safe to call multiple times — subsequent calls are no-ops. Called automatically by __exit__ and __del__.

count_matching_sequences(sequences: list[str]) int

Count how many of the given sequences already exist in the datastore.

Uses a single vectorized hash join instead of N individual lookups. Safe to call from any context — registers DataFrame explicitly.

create_pipeline_run(run_id: str, pipeline_type: str, config: dict, status: str = 'running')

Create or update a pipeline run record (safe for resume runs).

ensure_input_columns(columns: list[str])

Ensure the sequences table has all the given columns.

Uses ALTER TABLE ADD COLUMN for any that don’t already exist. This is idempotent — safe to call on every pipeline run.

Raises:
duckdb.CatalogException: If column creation fails for an unexpected

reason (e.g. type conflict). The “column already exists” case is silently ignored as it is the normal idempotent path.

execute_filter_sql(sequence_ids: list[int], sql_query: str, ws_table_name: str = '_filter_ws') list[int]

Execute a filter SQL query and return surviving sequence IDs.

The sql_query must be a complete SELECT statement that returns sequence_id values and JOINs against ws_table_name (which contains the input sequence_ids) to scope to the working set.

Pass a unique ws_table_name (use make_filter_ws_name()) when running multiple SQL-native filters concurrently — the default _filter_ws name races otherwise.

Args:

sequence_ids: Input sequence IDs. sql_query: Complete SQL SELECT returning sequence_id values. ws_table_name: Name to register the working-set IDs under. Must

match the ws_table= argument passed to the filter’s to_sql().

Returns:

List of sequence_ids that survive the filter.

Raises:

ValueError: If sql_query is not a single SELECT statement.

export_to_csv(path: str | Path, **kwargs) None

Export all data to CSV (convenience wrapper around export_to_dataframe).

export_to_dataframe(include_sequences: bool = True, include_predictions: bool = True, include_generation_metadata: bool = False, prediction_types: list[str] | None = None, run_id: str | None = None) DataFrame

Export data to a flat DataFrame using a single DuckDB SQL query.

Uses conditional aggregation (CASE WHEN pivot) — no per-type queries, no pandas merges, no full table loads.

Args:

include_sequences: Always True; includes sequence_id, sequence, length. include_predictions: Pivot prediction_type values into columns. include_generation_metadata: Join generation_metadata columns. prediction_types: Limit to specific prediction types (None = all).

Returns:

Wide-format DataFrame: one row per sequence, one column per prediction type.

export_to_parquet(table_name: str, output_path: str | Path)

Export table to Parquet file (for sharing/archiving).

Args:

table_name: Table to export (sequences, predictions, etc.) output_path: Path to output Parquet file

Example:
default
>>> ds.export_to_parquet('sequences', 'sequences_backup.parquet')
get_all_sequences() DataFrame

Return all sequences as a DataFrame with sequence_id, sequence, length columns.

get_column_registry_entry(column_name: str) dict | None

Return the registry entry for a column, or None if not registered.

get_context(run_id: str, key: str) Any | None

Retrieve a value from the pipeline context table.

get_embedding(embedding_id: int) tuple | None

Return (metadata_dict, embedding_array) for a given embedding_id, or None.

get_embeddings_bulk(sequence_ids: list[int], model_name: str | None = None) dict[int, numpy.ndarray]

Fetch embeddings for multiple sequences in a single JOIN query.

Replaces N individual get_embeddings_by_sequence() calls (O(n) queries → O(1)).

Args:

sequence_ids: List of sequence IDs to fetch. model_name: Optional model filter.

Returns:

Dict mapping sequence_id → numpy embedding array for sequences that have an embedding.

get_embeddings_by_sequence(sequence: str, model_name: str | None = None, load_data: bool = False) list[dict]

Get embeddings for a sequence.

get_embeddings_concat(sequence_ids: list[int], model_names: list[str]) dict[int, numpy.ndarray]

Fetch and concatenate embeddings from multiple models per sequence.

For each sequence_id, retrieves the embedding from each model in model_names order and horizontally concatenates them into a single vector. Sequences missing an embedding from any requested model are omitted from the result.

Args:

sequence_ids: Sequence IDs to fetch. model_names: Ordered list of model names whose embeddings will be

concatenated (e.g. ["esm2-8m", "esmc-300m"]).

Returns:

Dict mapping sequence_id → concatenated numpy array.

get_existing_input_columns() list[str]

Return extra columns on the sequences table (beyond the base schema).

Used for input-schema validation when connecting to an existing DB. Returns an empty list if only the base columns are present.

get_filter_results(run_id: str, stage_name: str) list[int]

Return sequence_ids that passed a given filter stage in a run.

Args:

run_id: Pipeline run ID. stage_name: Filter stage name.

Returns:

List of sequence_ids that passed the filter, or empty list if no data.

get_generation_metadata(sequence_id: int) list[dict]

Return generation metadata records for a sequence_id.

get_latest_definition_id() str | None

Return the definition_id of the most recently created pipeline definition.

get_latest_run_id(definition_id: str | None = None) str | None

Return the run_id of the most recent pipeline run.

from_db() must reuse the existing run_id so that resume can find already-completed stages. A new run_id means no stages are marked complete, causing everything to re-run.

Args:
definition_id: If provided, restrict to runs that used this definition.

If None, return the most recent run across all definitions.

Returns:

The run_id string, or None if no runs exist.

get_pipeline_metadata(key: str) Any | None

Retrieve a value from pipeline_metadata by key.

get_pipeline_run(run_id: str) dict | None

Return pipeline run record as a dict, or None if not found.

get_predictions(sequence_id: int, prediction_type: str | None = None, model_name: str | None = None) DataFrame

Return predictions for a sequence_id as a DataFrame.

get_predictions_bulk(sequence_ids: list[int], prediction_type: str, model_name: str) DataFrame

Fetch predictions for multiple sequences in a single JOIN query.

Replaces N individual get_predictions_by_sequence() calls.

Returns:

DataFrame with columns: sequence_id, value, metadata

get_predictions_by_sequence(sequence: str, prediction_type: str | None = None, model_name: str | None = None) DataFrame

Get predictions for a sequence.

get_sequence(sequence_id: int) str | None

Return the sequence string for a given sequence_id, or None if not found.

get_sequence_attributes_for_ids(sequence_ids: list[int], attr_names: list[str]) dict[int, dict[str, str]]

Retrieve per-sequence attributes, returning {seq_id: {attr: value}}.

Args:

sequence_ids: Sequence IDs to look up. attr_names: Attribute names to retrieve.

Returns:

Nested dict: {sequence_id: {attr_name: attr_value}}.

get_sequence_id(sequence: str) int | None

Get sequence_id for a sequence.

get_sequence_ids_with_prediction(sequence_ids: list[int], prediction_type: str, model_name: str) list[int]

Return sequence_ids that DO have a given prediction (inverse of uncached check).

Args:

sequence_ids: Candidate sequence IDs. prediction_type: Prediction type key. model_name: Model name.

Returns:

List of sequence_ids that have a cached prediction.

get_sequences_for_ids(sequence_ids: list[int]) list[tuple[int, str]]

Fetch (sequence_id, sequence) pairs for the given IDs.

Lightweight fetch for building API request items without materializing a full DataFrame.

Args:

sequence_ids: List of sequence IDs to look up.

Returns:

List of (sequence_id, sequence_string) tuples.

get_sequences_for_ids_with_columns(sequence_ids: list[int], columns: list[str]) dict[int, dict[str, str]]

Fetch column values from the sequences table for given IDs.

This reads columns stored directly on the sequences table (via ensure_input_columns), NOT from sequence_attributes.

Args:

sequence_ids: Sequence IDs to look up. columns: Column names to fetch (must exist on the sequences table).

Returns:

{sequence_id: {col: value, ...}}

get_stats() dict[str, int]

Return row counts for the main tables.

get_structure(sequence_id: int, model_name: str | None = None) dict | None

Fetch the most recent structure for a sequence, decompressing on read.

Returns a dict with ‘structure_str’ key (always decompressed string) regardless of whether data was stored compressed (structure_data BLOB) or as legacy plain TEXT.

Args:

sequence_id: Sequence ID. model_name: Optional model filter.

Returns:

Dict with structure record, or None if not found.

get_structure_by_id(structure_id: int) dict | None

Return a structure record by its structure_id (primary key).

get_structures_bulk(sequence_ids: list[int]) DataFrame

Fetch structures for multiple sequences, decompressing structure content.

Returns a DataFrame with a ‘structure_str’ column (always plain text) regardless of whether data was stored compressed (structure_data BLOB) or as legacy plain TEXT.

Args:

sequence_ids: List of sequence IDs to look up.

Returns:

DataFrame with one row per structure record.

get_structures_by_sequence(sequence: str, model_name: str | None = None) list[dict]

Return structure records for a sequence string (decompressed).

get_structures_for_ids(sequence_ids: list[int], model_name: str) dict[int, dict]

Fetch the most recent structure for each sequence_id in one query.

Returns {sequence_id: record} where record has a ‘structure_str’ key. Replaces N individual get_structure() calls for batch structure injection.

get_uncached_sequence_ids(sequence_ids: list[int], prediction_type: str, model_name: str) list[int]

Return sequence_ids that do NOT yet have a given prediction (vectorized anti-join).

Replaces N individual has_prediction() calls with a single SQL query.

Args:

sequence_ids: Candidate sequence IDs to check. prediction_type: Prediction type key. model_name: Model name.

Returns:

List of sequence_ids with no cached prediction.

has_prediction(sequence: str, prediction_type: str, model_name: str) bool

Check if prediction exists for sequence.

is_stage_complete(stage_id: str) bool

Check if stage is complete.

load_blob(blob_id: str) str | None

Retrieve a stored blob by its blob_id. Returns None if not found.

load_pipeline_definition(definition_id: str | None = None) dict | None

Load a pipeline definition by ID, or the latest one if ID is None.

make_filter_ws_name() str

Mint a unique _filter_ws_ table name for one filter execution.

Required when SQL-native filters can run in parallel at the same DAG level — sharing the canonical _filter_ws registration would let two stages clobber each other’s working set.

mark_stage_complete(run_id: str, stage_name: str, stage_id: str, input_count: int, output_count: int, status: str = 'completed')

Mark stage as complete (or failed/skipped).

uses INSERT … ON CONFLICT DO NOTHING so that a stage that was already marked ‘completed’ does not get its completed_at timestamp overwritten on resume. Only truly new rows are inserted.

materialize_working_set(ws: WorkingSet, include_predictions: bool = True, prediction_types: list[str] | None = None) pd.DataFrame

Materialize a WorkingSet into a DataFrame via a single DuckDB pivot query.

Args:

ws: WorkingSet containing the sequence IDs to materialize. include_predictions: If True, pivot prediction values into columns. prediction_types: Limit to specific types (None = all available).

Returns:

Wide-format DataFrame: one row per sequence, one column per prediction type. Always includes the following columns regardless of pipeline configuration:

  • sequence_id, sequence, length, hash — core sequence data.

  • source_label — label set on the generation config (e.g. DirectGenerationConfig.label, SaturationMutagenesisConfig.label). NULL / None when no label was supplied. This column is always present even when no labels have been set, so downstream code should not branch on "source_label" in df.columns — it is always there.

query(sql: str, params: list | None = None) DataFrame

Execute arbitrary SQL query and return DataFrame.

This is the POWER feature - query directly without loading everything!

Args:

sql: DuckDB SQL query params: Optional query parameters

Returns:

DataFrame with results (only loads what matches query!)

Example:
default
>>> # Find high-quality long sequences
>>> df = ds.query('''
...     SELECT s.sequence, p.value as plddt
...     FROM sequences s
...     JOIN predictions p ON s.sequence_id = p.sequence_id
...     WHERE s.length > 200
...     AND p.prediction_type = 'plddt'
...     AND p.value > 80
... ''')
register_column(column_name: str, model_name: str, action: str, definition_id: str, stage_name: str)

Register an output column in the prediction_column_registry. Idempotent.

save_filter_results(run_id: str, stage_name: str, passed_sequence_ids: list[int])

Record which sequence_ids passed a filter stage (for resume support).

Args:

run_id: Pipeline run ID. stage_name: Filter stage name. passed_sequence_ids: IDs of sequences that passed the filter.

save_pipeline_definition(definition_id: str, pipeline_type: str, input_schema_json: str | None, stages_json: str)

Persist a pipeline definition. Updates stages/schema if definition_id already exists.

set_context(run_id: str, key: str, value: Any)

Store a key-value pair in the pipeline context table.

set_pipeline_metadata(key: str, value: Any)

Upsert a key/value pair in the pipeline_metadata table.

store_blob(content: str) str

Store a large string value, returning its blob_id (SHA-256[:32]).

Content-addressed: identical content always maps to the same blob_id. Safe to call multiple times with the same content (INSERT OR IGNORE).

store_sequence_attributes(seq_ids: list[int], attr_name: str, attr_values: list[str])

Persist a per-sequence attribute column (e.g. heavy_chain, light_chain).

Args:

seq_ids: Sequence IDs. attr_name: Attribute name (column name from the input DataFrame). attr_values: Corresponding values (one per sequence_id).

update_pipeline_run_status(run_id: str, status: str)

Update pipeline run status.

biolm.pipeline.filters module

Filter implementations for pipeline stages.

Filters can be: - Per-sequence: Operate on individual sequences independently (streaming-compatible) - Aggregate: Require all data before filtering (must batch)

class biolm.pipeline.filters.BaseFilter

Bases: ABC

Base class for filters.

Attributes:
requires_complete_data: If True, filter needs all data before filtering.

If False, can filter sequences as they arrive (streaming).

requires_complete_data: bool = False
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) str | None

Return a complete SQL SELECT that yields surviving sequence_id values.

The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single sequence_id column). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.

Args:

ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.

Example return value:

default
SELECT w.sequence_id
FROM _filter_ws w
INNER JOIN predictions p ON w.sequence_id = p.sequence_id
WHERE p.prediction_type = 'tm' AND p.value >= 60.0

Returns None (default) when the filter cannot be expressed as SQL. Filters that return None will be executed via DataFrame materialization.

class biolm.pipeline.filters.CompositeFilter(*filters: BaseFilter)

Bases: BaseFilter

A serializable filter that chains sub-filters sequentially.

Unlike CustomFilter, CompositeFilter is fully serializable via to_spec() / filter_from_spec() as long as every sub-filter is serializable. It also supports SQL fast-path evaluation when all sub-filters implement to_sql() and exactly one SQL filter is present.

Args:

*filters: Sub-filters applied left to right.

Example:
default
>>> combined = CompositeFilter(
...     ThresholdFilter('tm', min_value=60),
...     SequenceLengthFilter(min_length=100),
... )
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

to_sql(ws_table: str = '_filter_ws', **kwargs) str | None

Return SQL only when a single sub-filter supports it.

Full CTE-chaining for multiple SQL filters is complex to implement correctly (each stage’s output must feed the next as a temporary table). For now we support the single-SQL-filter case and fall back to DataFrame materialization for all other combinations.

class biolm.pipeline.filters.ConservedResidueFilter(conserved_positions: dict[int, list[str]], reference_length: int | None = None)

Bases: BaseFilter

Filter sequences that have specific residues at specific positions.

Args:
conserved_positions: Dict mapping position (0-indexed) to allowed residues

e.g., {5: [‘M’, ‘L’], 10: [‘K’]}

reference_length: Expected sequence length (optional)

Example:
default
>>> filter = ConservedResidueFilter({107: ['H'], 109: ['H'], 126: ['H']})
>>> df_filtered = filter(df)
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

class biolm.pipeline.filters.CustomFilter(func: Callable[[DataFrame], DataFrame], name: str | None = None)

Bases: BaseFilter

Apply a custom filter function.

Args:

func: Function that takes a DataFrame and returns a filtered DataFrame name: Optional name for the filter (for repr)

Example:
default
>>> def my_filter(df):
...     return df[df['sequence'].str.contains('M')]
>>> filter = CustomFilter(my_filter, name='contains_M')
>>> df_filtered = filter(df)
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

class biolm.pipeline.filters.DiversitySamplingFilter(n_samples: int, method: str = 'random', score_column: str | None = None, random_seed: int | None = 42, resample: bool = True)

Bases: BaseFilter

Sample diverse sequences using clustering or random sampling.

This filter REQUIRES complete data to assess diversity.

Args:

n_samples: Number of sequences to sample method: Sampling method (‘random’, ‘spread’, ‘top’) score_column: Column to use for ‘top’ method random_seed: Random seed for reproducibility resample: If False, only sample if not already sampled (default: True)

Example:
default
>>> filter = DiversitySamplingFilter(n_samples=1000, method='random')
>>> df_sampled = filter(df)
requires_complete_data: bool = True
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

class biolm.pipeline.filters.HammingDistanceFilter(reference_sequence: str, max_distance: float | None = None, min_distance: float | None = None, normalize: bool = False)

Bases: BaseFilter

Filter by Hamming distance to a reference sequence.

Args:

reference_sequence: Reference sequence max_distance: Maximum Hamming distance (inclusive) min_distance: Minimum Hamming distance (inclusive) normalize: If True, use normalized distance (0-1)

Example:
default
>>> filter = HammingDistanceFilter('MKTAYIAKQ', max_distance=5)
>>> df_filtered = filter(df)
static hamming_distance(seq1: str, seq2: str, normalize: bool = False) float

Calculate Hamming distance between two sequences.

For equal-length sequences this is the standard Hamming distance (count of positions where characters differ).

For sequences of different lengths (F09 — edge-case note): the distance is computed as the number of mismatches in the overlapping prefix PLUS the absolute difference in lengths (each extra character in the longer sequence counts as one mismatch). When normalize=True the result is divided by max(len(seq1), len(seq2)). This is a non-standard extension; callers comparing variable-length sequences should be aware that the normalized value is NOT equivalent to edit distance / Levenshtein normalized distance.

to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

class biolm.pipeline.filters.RankingFilter(column: str, n: int | None = None, ascending: bool = False, method: str = 'top', percentile: float | None = None)

Bases: BaseFilter

Filter by ranking - select top N or bottom N by a column value.

This filter REQUIRES complete data to rank all sequences.

Args:

column: Column name to rank by n: Number of sequences to select ascending: If True, select lowest values; if False, select highest (default) method: Ranking method (‘top’ for top N, ‘bottom’ for bottom N, ‘percentile’ for top/bottom %) percentile: If method=’percentile’, the percentile threshold (0-100)

Example:
default
>>> # Top 100 by Tm
>>> filter = RankingFilter('tm', n=100, ascending=False)
>>> df_filtered = filter(df)
>>>
>>> # Bottom 50 by hamming distance
>>> filter = RankingFilter('hamming_distance', n=50, ascending=True)
>>> df_filtered = filter(df)
>>>
>>> # Top 10% by pLDDT
>>> filter = RankingFilter('plddt', method='percentile', percentile=90)
>>> df_filtered = filter(df)
requires_complete_data: bool = True
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) str | None

Return a complete SQL SELECT that yields surviving sequence_id values.

The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single sequence_id column). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.

Args:

ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.

Example return value:

default
SELECT w.sequence_id
FROM _filter_ws w
INNER JOIN predictions p ON w.sequence_id = p.sequence_id
WHERE p.prediction_type = 'tm' AND p.value >= 60.0

Returns None (default) when the filter cannot be expressed as SQL. Filters that return None will be executed via DataFrame materialization.

class biolm.pipeline.filters.SequenceLengthFilter(min_length: int | None = None, max_length: int | None = None)

Bases: BaseFilter

Filter by sequence length.

Args:

min_length: Minimum sequence length (inclusive) max_length: Maximum sequence length (inclusive)

Example:
default
>>> filter = SequenceLengthFilter(min_length=50, max_length=500)
>>> df_filtered = filter(df)
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) str | None

Return a complete SQL SELECT that yields surviving sequence_id values.

The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single sequence_id column). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.

Args:

ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.

Example return value:

default
SELECT w.sequence_id
FROM _filter_ws w
INNER JOIN predictions p ON w.sequence_id = p.sequence_id
WHERE p.prediction_type = 'tm' AND p.value >= 60.0

Returns None (default) when the filter cannot be expressed as SQL. Filters that return None will be executed via DataFrame materialization.

class biolm.pipeline.filters.ThresholdFilter(column: str, min_value: float | None = None, max_value: float | None = None, keep_na: bool = False)

Bases: BaseFilter

Filter by column value threshold.

Args:

column: Column name to filter on min_value: Minimum value (inclusive) max_value: Maximum value (inclusive) keep_na: Whether to keep rows with NaN values

Example:
default
>>> filter = ThresholdFilter('tm', min_value=60.0)
>>> df_filtered = filter(df)
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) str | None

Return a complete SQL SELECT that yields surviving sequence_id values.

The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single sequence_id column). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.

Args:

ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.

Example return value:

default
SELECT w.sequence_id
FROM _filter_ws w
INNER JOIN predictions p ON w.sequence_id = p.sequence_id
WHERE p.prediction_type = 'tm' AND p.value >= 60.0

Returns None (default) when the filter cannot be expressed as SQL. Filters that return None will be executed via DataFrame materialization.

class biolm.pipeline.filters.ValidAminoAcidFilter(alphabet: str = 'ACDEFGHIKLMNPQRSTVWY', verbose: bool = True, column: str = 'sequence')

Bases: BaseFilter

Filter sequences to only those composed of valid amino acid characters.

Uses vectorized regex matching via str.match() (C-level regex engine), which is ~100x faster than .apply(lambda) at million-sequence scale.

Args:

alphabet: String of allowed characters (default: 20 standard amino acids) verbose: If True, print count of removed sequences

Example:
default
>>> filter = ValidAminoAcidFilter()
>>> df_filtered = filter(df)
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) str | None

Return a complete SQL SELECT that yields surviving sequence_id values.

The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single sequence_id column). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.

Args:

ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.

Example return value:

default
SELECT w.sequence_id
FROM _filter_ws w
INNER JOIN predictions p ON w.sequence_id = p.sequence_id
WHERE p.prediction_type = 'tm' AND p.value >= 60.0

Returns None (default) when the filter cannot be expressed as SQL. Filters that return None will be executed via DataFrame materialization.

biolm.pipeline.filters.combine_filters(*filters: BaseFilter) BaseFilter

Combine multiple filters into a single filter (applied sequentially).

Returns a CompositeFilter which is serializable (unlike the previous CustomFilter-based implementation) and supports SQL fast-path evaluation when all sub-filters implement to_sql().

Args:

*filters: Variable number of filter objects

Returns:

Combined filter

Example:
default
>>> filter = combine_filters(
...     ThresholdFilter('tm', min_value=60),
...     SequenceLengthFilter(min_length=100)
... )

biolm.pipeline.generative module

Generative pipeline for sequence generation using language models.

Supports: - Masked language models (ESM, ESM-1v) with remasking - Inherently generative models (ProteinMPNN, ProGen2, etc.) - Temperature and sampling parameter scanning - Multi-model generation in parallel - DMS-style scanning: SaturationMutagenesisConfig (single-mutant library + scoring)

and IterativeMaskingDMSConfig (greedy MLM argmax 2-point DMS)

  • Structured config hierarchy: ScoringProtocolConfig / GenerativeProtocolConfig base classes

class biolm.pipeline.generative.DirectGenerationConfig(model_name: str, structure_path: str | None = None, structure_column: str | None = None, sequence: str | None = None, item_field: str = 'pdb', params: dict[str, typing.Any] = <factory>, num_sequences: int = 100, temperature: float = 1.0, structure_from_stage: str | None = None, structure_from_model: str | None = None, n_runs: int = 1, label: str | None = None)

Bases: GenerativeProtocolConfig

Configuration for structure- or sequence-conditioned generation.

Use with models such as ProteinMPNN, AntiFold, HyperMPNN, LigandMPNN, DSM.

The caller is responsible for providing the correct item_field and params for the target model — these vary per model and are documented in the BioLM API schema (/schema//generate/). Common values:

Args:
model_name: BioLM model slug (e.g. 'protein-mpnn', 'antifold',

'dsm-150m-base').

structure_path: Path to a PDB or CIF file. structure_column: DataFrame column holding PDB strings (for chained

pipelines where structure was predicted upstream).

sequence: Parent sequence string for sequence-conditioned models (DSM). item_field: The item dict key expected by the model API — 'pdb' for

structure-conditioned models, 'sequence' for DSM. Defaults to 'pdb'.

params: Model-specific params dict (required for non-trivial calls).

Keys must exactly match the model’s API param names. When empty the stage sends {'num_sequences': num_sequences, 'temperature': temperature} as a simple fallback — useful only for models that accept these exact param names.

num_sequences: Fallback when params is empty (default 100). temperature: Fallback when params is empty (default 1.0).

Example:

default
from biolm.pipeline.generative import DirectGenerationConfig, GenerativePipeline

# Structure-conditioned design with ProteinMPNN
cfg_mpnn = DirectGenerationConfig(
    model_name="protein-mpnn",
    structure_path="/path/to/protein.pdb",
    item_field="pdb",
    params={"num_sequences": 50, "temperature": 0.1},
)

# Sequence-conditioned design with DSM
cfg_dsm = DirectGenerationConfig(
    model_name="dsm-150m-base",
    sequence="MKTAYIAKQRQ",
    item_field="sequence",
    params={
        "num_sequences": 100,
        "temperature": 1.0,
        "remasking": "low_confidence",
        "step_divisor": 8,
    },
)

pipeline = GenerativePipeline(configs=[cfg_mpnn])
results = pipeline.run()
item_field: str = 'pdb'
label: str | None = None
model_name: str
n_runs: int = 1
num_sequences: int = 100
params: dict[str, Any]
sequence: str | None = None
structure_column: str | None = None
structure_from_model: str | None = None
structure_from_stage: str | None = None
structure_path: str | None = None
temperature: float = 1.0
to_spec() dict

Return a serializable dict for pipeline definition persistence.

class biolm.pipeline.generative.FoldingEntity(id: str, entity_type: str, sequence: str | None = None, smiles: str | None = None, ccd: str | None = None)

Bases: object

A molecular entity for co-folding models (Boltz2, Chai-1).

Used with DataPipeline.add_cofolding_prediction() and GenerativePipeline.add_cofolding_prediction() to inject static entities — ligands, cofactors, DNA/RNA strands, or additional protein chains — alongside the pipeline’s primary sequences.

Args:

id: Chain identifier (Boltz uses single letter(s); Chai-1 uses a name). entity_type: Molecule type — 'protein', 'dna', 'rna', or

'ligand'.

sequence: Amino-acid / nucleotide sequence for protein / DNA / RNA entities. smiles: SMILES string for small-molecule ligands. ccd: CCD code for ligands defined in the Chemical Component Dictionary

(e.g. 'ATP', 'HEM').

ccd: str | None = None
entity_type: str
id: str
sequence: str | None = None
smiles: str | None = None
biolm.pipeline.generative.Generate(model_name: str, num_sequences: int = 100, temperature: float | list[float] = 1.0, parent_sequence: str | None = None, **kwargs) DataFrame

Convenience function for quick sequence generation.

Args:

model_name: BioLM model name num_sequences: Number of sequences to generate temperature: Temperature (float) or list of temperatures for a temperature scan.

When a list is given, one DirectGenerationConfig is created per temperature.

parent_sequence: Optional parent sequence for sequence-conditioned models **kwargs: Additional arguments forwarded to GenerativePipeline

Returns:

DataFrame with generated sequences

Example:
default
>>> df = Generate('dsm-150m-base', num_sequences=100, parent_sequence='MKTAY')
class biolm.pipeline.generative.GenerationConfig(model_name: str, num_sequences: int = 100, temperature: float | list[float] = 1.0, sampling_params: dict[str, typing.Any] = <factory>, generation_method: str = 'generate', parent_sequence: str | None = None, mask_positions: str | list[int] = 'auto', mask_fraction: float = 0.15, batch_size: int = 32)

Bases: object

Configuration for sequence generation.

Deprecated since version Use: RemaskingConfig for MLM-based generation or DirectGenerationConfig for structure-conditioned models.

Args:

model_name: BioLM model name num_sequences: Number of sequences to generate temperature: Temperature or list of temperatures to scan sampling_params: Additional sampling parameters generation_method: ‘generate’ or ‘remask’ (for MLMs) parent_sequence: Parent sequence (for remasking or conditioning) mask_positions: Positions to mask (for remasking), or ‘auto’ for automatic mask_fraction: Fraction of positions to mask (if mask_positions=’auto’) batch_size: Batch size for generation

batch_size: int = 32
generation_method: str = 'generate'
mask_fraction: float = 0.15
mask_positions: str | list[int] = 'auto'
model_name: str
num_sequences: int = 100
parent_sequence: str | None = None
sampling_params: dict[str, Any]
temperature: float | list[float] = 1.0
class biolm.pipeline.generative.GenerationStage(name: str = 'generation', config: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig | None = None, configs: list[Union[biolm.pipeline.generative.GenerationConfig, biolm.pipeline.mlm_remasking.RemaskingConfig, biolm.pipeline.generative.DirectGenerationConfig, biolm.pipeline.generative.SaturationMutagenesisConfig, biolm.pipeline.generative.IterativeMaskingDMSConfig]] | None = None, deduplicate: bool = True, **kwargs)

Bases: Stage

Stage for generating sequences using generative models.

Accepts either the new typed configs (RemaskingConfig / DirectGenerationConfig) or the legacy GenerationConfig list.

For DirectGenerationConfig the caller supplies the correct item_field (e.g. 'pdb' or 'sequence') and params dict with model-specific param names — no auto-detection is performed.

The stage handles the three main response formats returned by BioLM generation models:

  • Flat list (MPNN): [{sequence, pdb, ...}, ...]

  • Nested samples (AntiFold): [{sequences: [{heavy, light, ...}]}]

  • Double-nested (DSM): [[{sequence, log_prob, ...}, ...]]

Args:

name: Stage name. config: Single RemaskingConfig or DirectGenerationConfig (new API). configs: List of config objects — GenerationConfig, RemaskingConfig, or

DirectGenerationConfig (old / multi-model API).

deduplicate: Whether to deduplicate generated sequences.

async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]

Generate sequences using configured models.

async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]

Generate sequences and return a WorkingSet of the new IDs.

The input WorkingSet’s IDs are forwarded as ws_ids so that DirectGenerationConfig can scope structure lookups to sequences already in the current pipeline run (GEN-05 consistency: no cross-run contamination when structure_from_stage is set).

to_spec() dict

Return a serializable dict for pipeline definition persistence.

class biolm.pipeline.generative.GenerativePipeline(generation_configs: list[Union[biolm.pipeline.generative.GenerationConfig, biolm.pipeline.mlm_remasking.RemaskingConfig, biolm.pipeline.generative.DirectGenerationConfig, biolm.pipeline.generative.SaturationMutagenesisConfig, biolm.pipeline.generative.IterativeMaskingDMSConfig]] | None = None, deduplicate: bool = True, configs: list[Union[biolm.pipeline.mlm_remasking.RemaskingConfig, biolm.pipeline.generative.DirectGenerationConfig, biolm.pipeline.generative.SaturationMutagenesisConfig, biolm.pipeline.generative.IterativeMaskingDMSConfig]] | None = None, filters=None, data_store=None, **kwargs)

Bases: BasePipeline

Pipeline for generating sequences and running predictions.

Supports: - Multiple generative models in parallel - Temperature scanning - Masked language model remasking - Downstream predictions and filtering

Args:

generation_configs: List of GenerationConfig objects deduplicate: Whether to deduplicate generated sequences datastore: DataStore instance or path run_id: Unique run ID output_dir: Output directory resume: Resume from previous run verbose: Enable verbose output

Example:
default
>>> # Generate with MPNN at multiple temperatures
>>> config1 = GenerationConfig(
...     model_name='proteinmpnn',
...     num_sequences=1000,
...     temperature=[0.5, 1.0, 1.5],
...     parent_sequence='MKTAYIAKQRQ'
... )
>>>
>>> # Also generate with ESM using remasking
>>> config2 = GenerationConfig(
...     model_name='esm2',
...     num_sequences=500,
...     generation_method='remask',
...     parent_sequence='MKTAYIAKQRQ',
...     mask_fraction=0.15
... )
>>>
>>> pipeline = GenerativePipeline(
...     generation_configs=[config1, config2]
... )
>>> pipeline.add_filter(ThresholdFilter('length', min_value=50))
>>> pipeline.add_prediction('esmfold', extractions='mean_plddt', columns='plddt')
>>> results = pipeline.run()
add_cofolding_prediction(model_name: str, action: str = 'predict', stage_name: str | None = None, prediction_type: str = 'structure', sequence_chain_id: str = 'A', sequence_entity_type: str = 'protein', static_entities: list[biolm.pipeline.generative.FoldingEntity] | None = None, depends_on: list[str] | None = None, params: dict | None = None, batch_size: int = 1)

Add a co-folding prediction stage (Boltz2, Chai-1).

Each sequence in the pipeline is treated as the primary protein chain. static_entities lets you inject additional molecules — ligands, cofactors, DNA/RNA strands, or extra protein chains — that are held constant across all designs.

Args:

model_name: BioLM model slug, e.g. 'boltz2' or 'chai1'. action: API action (default 'predict'). stage_name: Stage name (defaults to model_name). prediction_type: Column name for the confidence score (default

'structure').

sequence_chain_id: Chain ID assigned to the pipeline’s primary

sequence in the multi-molecule request (e.g. 'A').

sequence_entity_type: Molecule type for the primary sequence:

'protein', 'dna', or 'rna' (default 'protein').

static_entities: List of FoldingEntity objects — ligands,

cofactors, extra proteins/DNA/RNA — included in every request.

depends_on: Upstream stage names this stage waits for. params: Model-specific params dict (e.g.

{'recycling_steps': 3, 'sampling_steps': 20} for Boltz).

batch_size: Sequences per API request (default 1; co-folding models

are typically batch-size-1).

Example:

default
pipeline.add_cofolding_prediction(
    model_name='boltz2',
    static_entities=[
        FoldingEntity(id='L', entity_type='ligand', smiles='c1ccccc1'),
        FoldingEntity(id='B', entity_type='protein',
                      sequence='MKTAYIAKQRQ'),
    ],
    depends_on=['filter_top50'],
)
add_filter(filter_func, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)

Add a filter stage (same as DataPipeline).

add_generation_config(config: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig) GenerativePipeline

Append a config to the existing generation slot.

Use this to add a second model or temperature variant alongside the current generation config rather than replacing it. If there is no generation slot yet, one is created.

Args:

config: Config to add.

Returns:

Self, for method chaining.

add_prediction(model_name: str, action: str = 'predict', extractions=None, columns=None, params: dict | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)

Add a prediction stage (same as DataPipeline).

add_predictions(models: list[Union[str, dict]], action: str = 'predict', depends_on: list[str] | None = None, **kwargs)

Add multiple prediction stages at the same level (run in parallel).

Args:

models: List of model names or dicts with model configs action: Default action if not specified in dict depends_on: List of stage names all these depend on **kwargs: Default kwargs for all stages

Returns:

self for chaining

Example:
default
>>> pipeline.add_predictions(['temberture-regression', 'proteinmpnn', 'esm2'])
add_stage(stage: Stage) None

Add a stage to the pipeline.

If stage is a GenerationStage it replaces the current generation slot (there is always exactly one, at position 0) rather than appending. All other stage types are appended normally.

replace_generation(config: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig, stage_name: str | None = None, deduplicate: bool = True) GenerativePipeline

Swap the generation slot with a single new config.

Equivalent to set_generation(config, stage_name=stage_name). Kept for backwards compatibility and single-config convenience.

async run_async(**kwargs) dict[str, biolm.pipeline.base.StageResult]

Run the generative pipeline.

All GenerationStages (regardless of position) are extracted and run first as sources — their outputs are unioned into the initial WorkingSet so generated sequences trickle through every downstream prediction/filter stage (the “funnel”). Idempotent: self.stages is always restored after execution.

set_generation(*configs: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig, stage_name: str = 'generation', deduplicate: bool = True) GenerativePipeline

Set (or replace) the generation slot with one or more configs.

Multiple configs run in parallel — use this for multi-model generation or temperature scanning. Every call to .run() re-runs generation from scratch; downstream stages use their prediction cache so only truly new sequences are computed.

Args:
*configs: One or more RemaskingConfig or

DirectGenerationConfig objects.

stage_name: Name for the generation stage (default "generation"). deduplicate: Deduplicate across all configs (default True).

Returns:

Self, for method chaining.

Example:

default
# Single model
pipeline.set_generation(
    DirectGenerationConfig("dsm-150m-base", sequence=parent, num_sequences=200)
).run()

# Two models in parallel — sequences from both trickle through the funnel
pipeline.set_generation(
    DirectGenerationConfig("dsm-150m-base", sequence=parent, num_sequences=100),
    RemaskingConfig("esm-150m", mask_fraction=0.15),
).run()
use_sequences(sequences=None, column: str = 'sequence', stage_name: str = 'data_source', from_db: bool = False) GenerativePipeline

Use existing sequences as the pipeline source instead of generating.

Replaces the generation slot with a SequenceSourceConfig. Downstream prediction and filter stages run on the provided sequences, using the normal DuckDB prediction cache for anything already computed.

Args:

sequences: One of:

  • list[str] — plain amino-acid strings

  • pd.DataFrame — must contain column (default "sequence")

  • str / Path — CSV or FASTA file

  • None — reload all sequences already in the DuckDB

column: Column name when sequences is a DataFrame or CSV. stage_name: Name for the source stage (default "data_source"). from_db: Pull all sequences already present in this pipeline’s DuckDB.

Returns:

Self, for method chaining.

Example:

default
# Inject a list
pipeline.use_sequences(["MKTAY", "MKLLIV"]).run()

# Use all sequences already in the DB (e.g. recover + rerun)
pipeline.use_sequences(from_db=True).run()

# Load from CSV and run through the existing filter/predict stages
pipeline.use_sequences("candidates.csv").run()
class biolm.pipeline.generative.GenerativeProtocolConfig

Bases: object

Marker base class for stages that produce new amino-acid sequences.

Subclasses drive generative or masked-language models to emit novel sequences — either via autoregressive sampling (ProteinMPNN, DSM, AntiFold) or by greedy-argmax masking over an MLM (ESM2, ESMC). Unlike ScoringProtocolConfig subclasses, these stages expand the candidate pool rather than filtering it; they are the source stage in a generative design pipeline.

Use isinstance(config, GenerativeProtocolConfig)() to branch on config type in pipeline dispatch functions.

Example:

default
from biolm.pipeline.generative import (
    GenerativeProtocolConfig,
    DirectGenerationConfig,
    IterativeMaskingDMSConfig,
)

def dispatch(config):
    if isinstance(config, GenerativeProtocolConfig):
        return run_generative_stage(config)  # emits new sequences
    return run_scoring_stage(config)          # returns ranked variants

cfg_direct = DirectGenerationConfig(
    "dsm-150m-base", sequence="MKTAYIAKQRQ", num_sequences=50
)
cfg_dms = IterativeMaskingDMSConfig(
    parent_sequence="MKTAYIAKQRQ", model_name="esm2-650m"
)
assert isinstance(cfg_direct, GenerativeProtocolConfig)  # True
assert isinstance(cfg_dms, GenerativeProtocolConfig)     # True
class biolm.pipeline.generative.IterativeMaskingDMSConfig(parent_sequence: str, model_name: str, positions: list[int] | None = None, rounds: int = 2, mask_token: str = '<mask>', alphabet: str = 'ACDEFGHIKLMNPQRSTVWY', exclude_synonymous: bool = True, batch_size: int = 32, label: str | None = None, action: str = 'predict')

Bases: GenerativeProtocolConfig

Source config that builds multi-point variants via sequential greedy masking.

Implements an iterative argmax masking procedure using a masked language model:

  1. For each target position, mask it in the parent sequence and query the model for the highest-probability residue (greedy argmax — not sampled).

  2. If rounds > 1, apply the round-1 preferred substitution at each position and repeat for round 2: mask each other target position in the round-1 sequence and collect the argmax residue.

  3. Yield all resulting sequences as pipeline outputs.

This matches the ESM2 two-round DMS design pattern in the EGF generation notebook.

Args:

parent_sequence: Starting sequence. model_name: MLM model slug (e.g. 'esm2-650m', 'esmc-300m'). positions: 0-indexed positions to probe. Defaults to all positions. rounds: Number of sequential masking rounds (default 2). Round N uses

the variant produced by round N-1 as its starting sequence.

mask_token: Token inserted at masked positions (default ''). alphabet: Vocabulary used to identify valid AA positions in logits.

Defaults to the 20 canonical amino acids.

exclude_synonymous: Skip round-1 variants where the argmax matches WT

(default True).

batch_size: Sequences per API request (default 32). label: Optional label stored as source_label in results. action: API action for the model (default 'predict'; the model must

return logits). Only 'predict' is allowed because the greedy-argmax procedure requires per-token logit arrays.

Example:

default
from biolm.pipeline.generative import IterativeMaskingDMSConfig, GenerativePipeline

# Two-round greedy DMS at three positions using ESM2-650M
config = IterativeMaskingDMSConfig(
    parent_sequence="MKTAYIAKQRQ",
    model_name="esm2-650m",
    positions=[2, 5, 8],   # 0-indexed; None → all positions
    rounds=2,              # round 1: per-position; round 2: 2-point combos
    exclude_synonymous=True,
)

pipeline = GenerativePipeline(configs=[config])
results = pipeline.run()
# results DataFrame has columns:
#   sequence, dms_round, dms_pos1, dms_aa1, dms_pos2, dms_aa2
action: str = 'predict'
alphabet: str = 'ACDEFGHIKLMNPQRSTVWY'
batch_size: int = 32
exclude_synonymous: bool = True
label: str | None = None
mask_token: str = '<mask>'
model_name: str
parent_sequence: str
positions: list[int] | None = None
rounds: int = 2
to_spec() dict
class biolm.pipeline.generative.SaturationMutagenesisConfig(parent_sequence: str, scoring_model: str, positions: list[int] | None = None, alphabet: str = 'ACDEFGHIKLMNPQRSTVWY', scoring_action: str = 'predict', scoring_params: dict[str, typing.Any] = <factory>, score_field: str = 'ddg', top_n: int | None = 50, ascending: bool = True, exclude_synonymous: bool = True, batch_size: int = 8, label: str | None = None, pdb_str: str | None = None, chain: str = 'A')

Bases: ScoringProtocolConfig

Source config that generates a single-mutant library and filters by a prediction model.

Enumerates every single amino-acid substitution at the specified positions, scores each variant with scoring_model using scoring_action, then returns the top-top_n variants ranked by score_field.

Typical use: ThermoMPNN-D or ESM2StabP-guided design — predict ΔΔG for all single-point mutants, keep the most stabilising ones.

Args:

parent_sequence: Wild-type sequence to mutate. scoring_model: BioLM model slug used to score variants (e.g. 'thermompnn-d'). positions: 0-indexed positions to enumerate. If None, all positions

in the sequence are enumerated.

alphabet: Amino acids to substitute. Defaults to the 20 canonical AAs. scoring_action: API action for the scoring model (default 'predict'). scoring_params: Extra params forwarded to the scoring model API. score_field: Key inside each model response that holds the numeric score

(default 'ddg'). Supports nested access with '.' separator, e.g. 'result.ddg'.

top_n: Number of top-scoring variants to retain (default 50). None

keeps all variants that receive a valid score.

ascending: If True, lower scores are better (e.g. negative ΔΔG means

stabilising). Defaults to True.

exclude_synonymous: If True (default), skip substitutions that are

identical to the wild-type residue.

batch_size: Sequences per API request when scoring (default 8). label: Optional label stored as source_label in results. pdb_str: Raw PDB file contents as a string (not a file path). When

provided, each scoring item is built as {"pdb": pdb_str, "mutations": [""], "chain": chain} instead of {"sequence": mutant_sequence}. Required by structure-aware models such as ThermoMPNN-D. Pass None (default) for sequence-only models like ESM2StabP.

chain: Chain identifier forwarded in structure-aware scoring items

(default 'A').

Example:

default
from biolm.pipeline.generative import SaturationMutagenesisConfig, GenerativePipeline

# Score all single-mutant variants at three positions with ThermoMPNN-D
config = SaturationMutagenesisConfig(
    parent_sequence="MKTAYIAKQRQ",
    scoring_model="thermompnn-d",
    positions=[3, 7, 10],          # 0-indexed; None → all positions
    score_field="ddg",             # key in API response holding ΔΔG
    top_n=25,                      # keep top-25 most stabilising variants
    ascending=True,                # lower ΔΔG = more stabilising
    pdb_str=open("protein.pdb").read(),  # required by structure-aware models
)

pipeline = GenerativePipeline(configs=[config])
pipeline.add_prediction("esmfold", extractions="mean_plddt", columns="plddt")
results = pipeline.run()
alphabet: str = 'ACDEFGHIKLMNPQRSTVWY'
ascending: bool = True
batch_size: int = 8
chain: str = 'A'
exclude_synonymous: bool = True
label: str | None = None
parent_sequence: str
pdb_str: str | None = None
positions: list[int] | None = None
score_field: str = 'ddg'
scoring_action: str = 'predict'
scoring_model: str
scoring_params: dict[str, Any]
to_spec() dict
top_n: int | None = 50
class biolm.pipeline.generative.ScoringProtocolConfig

Bases: object

Marker base class for stages that score sequences and return a ranked subset.

Subclasses call a BioLM prediction model to assign a numeric score to each variant sequence enumerated by the config (e.g. all single-point mutants), then sort and return the top-N results. They do not produce wholly new sequences — the output is a scored, ranked subset of the candidate library built internally by the config (no separate generation step is needed).

The scoring_action field on concrete subclasses selects the client method: 'predict' calls BioLMApiClient.predict() and 'score' calls BioLMApiClient.score(). Both are allowlisted ('predict', 'score') to prevent arbitrary method dispatch.

Use isinstance(config, ScoringProtocolConfig)() to branch on config type in pipeline dispatch functions.

Example:

default
from biolm.pipeline.generative import (
    ScoringProtocolConfig,
    SaturationMutagenesisConfig,
)

def dispatch(config):
    if isinstance(config, ScoringProtocolConfig):
        return run_scoring_stage(config)  # returns ranked variant library
    return run_generative_stage(config)   # returns novel sequences

config = SaturationMutagenesisConfig(
    parent_sequence="MKTAYIAKQRQ",
    scoring_model="thermompnn-d",
    score_field="ddg",
)
assert isinstance(config, ScoringProtocolConfig)  # True
class biolm.pipeline.generative.SequenceSourceConfig(sequences: list | DataFrame | str | Path | None = None, column: str = 'sequence', from_db: bool = False)

Bases: object

Use existing sequences as the generation-slot source — no API calls made.

Plug into set_generation() (or use pipeline.use_sequences()) to feed existing data through prediction/filter stages without generating new sequences. The provided sequences are added to the DuckDB via the normal dedup path, so sequences already present just return their existing IDs.

Args:

sequences: Source of sequences — one of:

  • list[str]: plain amino-acid strings

  • pd.DataFrame: must contain column (default "sequence")

  • str / Path: path to a CSV or FASTA (.fasta/.fa) file

  • None: reload all sequences already in the DuckDB (requires from_db=True OR leaving sequences as None)

column: Column name when sequences is a DataFrame or CSV

(default "sequence").

from_db: Pull all sequences already present in the pipeline’s DuckDB

instead of loading new ones. Equivalent to sequences=None.

Example:

default
# Inject a list
pipeline.use_sequences(["MKTAY", "MKLLIV"]).run()

# Use all sequences already in the DB (e.g. after from_db() recovery)
pipeline.use_sequences(from_db=True).run()

# Load from CSV
pipeline.use_sequences("candidates.csv").run()
column: str = 'sequence'
from_db: bool = False
sequences: list | DataFrame | str | Path | None = None
to_spec() dict

Serialize for pipeline definition persistence.

Live DataFrames are not serializable — on reconstruct we fall back to from_db=True so the existing DB sequences are reused. Plain list[str] sequences are included directly so from_db() reconstruction can replay the same input without requiring an existing DB (GEN-09 fix).

biolm.pipeline.mlm_remasking module

Masked Language Model (MLM) remasking utilities.

Provides functionality for iterative remasking and prediction using masked language models like ESM, ESM-1v, ESM-2.

class biolm.pipeline.mlm_remasking.MLMRemasker(config: RemaskingConfig, api_client=None, model_name: str | None = None)

Bases: object

Masked Language Model remasking utility.

Handles iterative masking and prediction for generating sequence variants using masked language models.

Args:

config: RemaskingConfig instance api_client: BioLM API client (optional, for actual predictions) model_name: Model name (e.g., ‘esm2’, ‘esm1v’)

Example:
default
>>> config = RemaskingConfig(mask_fraction=0.15, num_iterations=5)
>>> remasker = MLMRemasker(config, model_name='esm2')
>>> variants = remasker.generate_variants('MKTAYIAKQRQ', num_variants=10)
create_masked_sequence(sequence: str, positions: list[int]) str

Create masked sequence with mask token at specified positions.

Args:

sequence: Original sequence positions: Positions to mask

Returns:

Masked sequence

async generate_variant(parent_sequence: str, iteration: int = 0) tuple[str, dict[str, Any]]

Generate a single variant through remasking (async).

Args:

parent_sequence: Starting sequence iteration: Iteration number (for seeding)

Returns:

Tuple of (variant_sequence, metadata_dict)

async generate_variants(parent_sequence: str, num_variants: int = 100, deduplicate: bool = True) list[tuple[str, dict[str, Any]]]

Generate multiple variants through remasking (async, concurrent).

Uses asyncio.gather with a semaphore to generate variants concurrently rather than sequentially, significantly reducing wall-clock time when the API client supports concurrent calls.

Args:

parent_sequence: Starting sequence num_variants: Number of variants to generate deduplicate: Remove duplicate sequences (parent excluded when True)

Returns:

List of (variant_sequence, metadata) tuples

async iterative_refinement(sequence: str, fitness_function: callable, num_iterations: int = 10, population_size: int = 20, keep_top_k: int = 5) list[tuple[str, float, dict[str, Any]]]

Perform iterative refinement using remasking and a fitness function (async).

Args:

sequence: Starting sequence fitness_function: Function that scores sequences (higher is better) num_iterations: Number of refinement iterations population_size: Number of variants per iteration keep_top_k: Number of top sequences to keep per iteration

Returns:

List of (sequence, fitness, metadata) tuples for final population

async predict_masked_positions(sequence: str, mask_positions: list[int]) tuple[str, dict[int, float]]

Predict amino acids at masked positions.

Builds the masked sequence client-side (inserting config.mask_token at each position), sends it to the model’s predict endpoint, and decodes the returned logits with temperature/top-k/top-p sampling.

Falls back to reading a "sequence" key from the response if the model returns a filled sequence directly instead of logits.

Args:

sequence: Original (unmasked) sequence. mask_positions: 0-indexed positions to replace.

Returns:

Tuple of (predicted_sequence, confidences_dict)

select_mask_positions(sequence: str, confidences: ndarray | None = None) list[int]

Select positions to mask based on strategy.

Args:

sequence: Input sequence confidences: Optional confidence scores per position (for low_confidence strategy)

Returns:

List of positions to mask (0-indexed)

class biolm.pipeline.mlm_remasking.RemaskingConfig(model_name: str = 'esm-150m', action: str = 'predict', mask_fraction: float = 0.15, mask_positions: str | list[int] = 'auto', num_iterations: int = 1, temperature: float = 1.0, top_k: int | None = None, top_p: float | None = None, mask_token: str = '<mask>', conserved_positions: list[int] | None = None, mask_strategy: str = 'random', block_size: int = 3, confidence_threshold: float = 0.8, parent_sequence: str | None = None, num_variants: int = 100)

Bases: object

Configuration for MLM remasking.

Args:

model_name: MLM model to use (e.g., ‘esm-150m’, ‘esm-650m’, ‘esm-3b’, ‘esm3’, ‘esmc’) mask_fraction: Fraction of positions to mask (default: 0.15) mask_positions: Specific positions to mask, or ‘auto’ for random num_iterations: Number of remasking iterations (default: 1) temperature: Sampling temperature (default: 1.0) top_k: Top-k sampling (default: None) top_p: Nucleus sampling (default: None) mask_token: Token to use for masking (default: ‘<mask>’) conserved_positions: Positions that should never be masked mask_strategy: Strategy for selecting positions (‘random’, ‘low_confidence’, ‘blocks’) block_size: Size of blocks for block masking (default: 3) confidence_threshold: Threshold for low-confidence masking (default: 0.8)

Example:
default
>>> # ESM2 150M remasking
>>> config = RemaskingConfig(
...     model_name='esm-150m',
...     mask_fraction=0.15,
...     num_iterations=5,
...     temperature=1.0
... )
>>>
>>> # ESM3 with higher temperature
>>> config = RemaskingConfig(
...     model_name='esm3',
...     mask_fraction=0.20,
...     temperature=1.5
... )
action: str = 'predict'
block_size: int = 3
confidence_threshold: float = 0.8
conserved_positions: list[int] | None = None
mask_fraction: float = 0.15
mask_positions: str | list[int] = 'auto'
mask_strategy: str = 'random'
mask_token: str = '<mask>'
model_name: str = 'esm-150m'
num_iterations: int = 1
num_variants: int = 100
parent_sequence: str | None = None
temperature: float = 1.0
to_spec() dict

Return a serializable dict for pipeline definition persistence.

top_k: int | None = None
top_p: float | None = None
biolm.pipeline.mlm_remasking.create_remasker_from_dict(config_dict: dict[str, Any], **kwargs) MLMRemasker

Create MLMRemasker from a configuration dictionary.

Args:

config_dict: Dictionary with configuration parameters **kwargs: Additional arguments for MLMRemasker

Returns:

MLMRemasker instance

Example:
default
>>> config = {
...     'mask_fraction': 0.2,
...     'num_iterations': 5,
...     'temperature': 1.0
... }
>>> remasker = create_remasker_from_dict(config, model_name='esm2')

biolm.pipeline.pipeline_def module

Pipeline definition persistence and reconstruction.

Provides factory functions for serializing pipeline definitions to DuckDB and reconstructing pipeline objects from stored definitions (for kernel-death recovery via DataPipeline.from_db() / GenerativePipeline.from_db()).

biolm.pipeline.pipeline_def.filter_from_spec(spec: dict) BaseFilter

Reconstruct a BaseFilter from its to_spec() dict.

Raises:

NotImplementedError: For CustomFilter (func is not serializable). ValueError: For unknown filter types.

biolm.pipeline.pipeline_def.pipeline_from_definition(defn: dict, datastore: DuckDBDataStore, run_id: str | None = None, verbose: bool = True) BasePipeline

Reconstruct a pipeline from a stored definition dict.

Args:

defn: Dict as returned by DuckDBDataStore.load_pipeline_definition(). datastore: Open DuckDB datastore to attach to the pipeline. run_id: Run ID to resume (None = look up the most recent run for this

definition, or generate a fresh one if no prior runs exist). when resuming via from_db(), we must reuse the existing run_id so that stage_id = f"{run_id}_{stage_name}" matches the stage_completions rows written by the previous run. Passing a new run_id causes all stages to appear incomplete and re-run.

verbose: Enable verbose output.

Returns:

A fully configured DataPipeline or GenerativePipeline ready to call .run(resume=True) on.

Raises:
NotImplementedError: If any stage cannot be auto-reconstructed

(e.g. CustomFilter, custom callable embedding_extractor).

ValueError: For unknown pipeline types.

biolm.pipeline.pipeline_def.stage_from_spec(spec: dict) Stage

Reconstruct a Stage from its to_spec() dict.

Raises:

NotImplementedError: For stage types that cannot be auto-reconstructed. ValueError: For unknown stage types.

biolm.pipeline.utils module

Utility functions for pipeline operations.

Also includes structure file conversion utilities (CIF ↔ PDB) for use with structure-conditioned generative models (AntiFold, HyperMPNN, etc.).

biolm.pipeline.utils.cif_to_pdb(cif_path: str, output_path: str | None = None) str

Convert a CIF structure file to PDB format.

Uses gemmi if available, falls back to biopython.

Args:

cif_path: Path to input CIF (.cif / .mmcif) file. output_path: Destination PDB path. Defaults to same name with .pdb extension.

Returns:

Path to written PDB file.

Raises:

ImportError: If neither gemmi nor biopython is installed.

biolm.pipeline.utils.compute_hamming_distance(seq1: str, seq2: str, normalize: bool = False) float

Compute Hamming distance between two sequences.

Args:

seq1: First sequence seq2: Second sequence normalize: If True, return normalized distance (0-1)

Returns:

Hamming distance

biolm.pipeline.utils.compute_sequence_identity(seq1: str, seq2: str) float

Compute sequence identity (fraction of identical positions).

Args:

seq1: First sequence seq2: Second sequence

Returns:

Sequence identity (0.0 to 1.0)

biolm.pipeline.utils.create_run_summary(pipeline) dict[str, Any]

Create a summary dict of pipeline run.

Args:

pipeline: Pipeline instance

Returns:

Summary dict

biolm.pipeline.utils.deduplicate_sequences(sequences: list[str] | DataFrame, sequence_column: str = 'sequence') list[str] | DataFrame

Remove duplicate sequences.

Args:

sequences: List of sequences or DataFrame sequence_column: Column name if DataFrame

Returns:

Deduplicated sequences (same type as input)

biolm.pipeline.utils.export_run_summary(pipeline, output_path: str | Path)

Export pipeline run summary to JSON.

Args:

pipeline: Pipeline instance output_path: Output file path

biolm.pipeline.utils.hash_sequence(sequence: str) str

Generate SHA256 hash of a sequence.

biolm.pipeline.utils.load_fasta(file_path: str | Path) list[str]

Load sequences from FASTA file.

Args:

file_path: Path to FASTA file

Returns:

List of sequences

biolm.pipeline.utils.load_sequences_from_file(file_path: str | Path, format: str | None = None) list[str]

Load sequences from a file.

Supports: - FASTA (.fasta, .fa, .faa) - CSV (.csv) with ‘sequence’ column - Plain text (one sequence per line)

Args:

file_path: Path to file format: Optional format override (‘fasta’, ‘csv’, ‘txt’)

Returns:

List of sequences

Example:
default
>>> sequences = load_sequences_from_file('sequences.fasta')
biolm.pipeline.utils.load_structure_string(path: str) tuple[str, str]

Load a structure file and return (format, content_string).

Args:

path: Path to a .pdb, .ent, .cif, or .mmcif file.

Returns:

Tuple of (format_str, content_str) where format_str is ‘pdb’ or ‘cif’.

Raises:

ValueError: If file extension is not recognised. FileNotFoundError: If file does not exist.

biolm.pipeline.utils.merge_prediction_results(df: DataFrame, predictions: dict[str, list[float]], prediction_names: list[str] | None = None) DataFrame

Merge prediction results into DataFrame.

Args:

df: Base DataFrame predictions: Dict mapping prediction names to value lists prediction_names: Optional list of column names

Returns:

DataFrame with predictions added

biolm.pipeline.utils.pdb_to_cif(pdb_path: str, output_path: str | None = None) str

Convert a PDB structure file to CIF format.

Uses gemmi if available, falls back to biopython.

Args:

pdb_path: Path to input PDB (.pdb / .ent) file. output_path: Destination CIF path. Defaults to same name with .cif extension.

Returns:

Path to written CIF file.

Raises:

ImportError: If neither gemmi nor biopython is installed.

biolm.pipeline.utils.sample_sequences(sequences: list[str] | DataFrame, n: int, method: str = 'random', score_column: str | None = None, random_seed: int | None = 42) list[str] | DataFrame

Sample n sequences from a collection.

Args:

sequences: List or DataFrame n: Number to sample method: ‘random’, ‘top’, or ‘spread’ score_column: Column for ‘top’ or ‘spread’ methods (DataFrame only) random_seed: Random seed

Returns:

Sampled sequences (same type as input)

biolm.pipeline.utils.split_sequences_by_length(sequences: list[str], boundaries: list[int]) dict[str, list[str]]

Split sequences into bins by length.

Args:

sequences: List of sequences boundaries: List of length boundaries [b1, b2, …]

Creates bins: <b1, b1-b2, b2-b3, …, >bn

Returns:

Dict mapping bin names to sequence lists

Example:
default
>>> bins = split_sequences_by_length(seqs, [100, 200, 300])
>>> # Returns: {'300': [...]}
biolm.pipeline.utils.summarize_dataframe(df: DataFrame) DataFrame

Create a summary of DataFrame columns.

Returns:

Summary DataFrame with statistics

biolm.pipeline.utils.validate_sequence(sequence: str, alphabet: str = 'protein') bool

Validate that a sequence contains only valid amino acids.

Args:

sequence: Sequence to validate alphabet: ‘protein’ or ‘dna’

Returns:

True if valid, False otherwise

biolm.pipeline.utils.write_fasta(sequences: list[str] | DataFrame, file_path: str | Path, headers: list[str] | None = None, sequence_column: str = 'sequence', header_column: str | None = None)

Write sequences to FASTA file.

Args:

sequences: List of sequences or DataFrame file_path: Output file path headers: Optional list of headers (one per sequence) sequence_column: Column name for sequences (if DataFrame) header_column: Column name for headers (if DataFrame)

Example:
default
>>> write_fasta(sequences, 'output.fasta')
>>> write_fasta(df, 'output.fasta', header_column='id')

biolm.pipeline.visualization module

Visualization utilities for pipeline results.

Provides plotting functions for: - Pipeline funnel diagrams - Prediction distributions - Embedding visualizations (PCA, UMAP) - Correlation matrices - Temperature scan results

class biolm.pipeline.visualization.PipelinePlotter(pipeline, df: DataFrame | None = None)

Bases: object

Convenience class for plotting pipeline results.

Args:

pipeline: Pipeline instance df: Optional DataFrame (uses pipeline.get_final_data() if not provided)

Example:
default
>>> plotter = PipelinePlotter(pipeline)
>>> plotter.plot_funnel()
>>> plotter.plot_distribution('tm')
plot_correlation_matrix(**kwargs)

Plot correlation matrix.

plot_distribution(column: str, **kwargs)

Plot distribution of a column.

plot_distributions(columns: list[str] | None = None, **kwargs)

Alias for plot_predictions — plot distributions of prediction columns.

plot_diversity(reference_sequence: str | None = None, **kwargs)

Plot sequence diversity.

plot_funnel(**kwargs)

Plot pipeline funnel.

plot_predictions(columns: list[str] | None = None, **kwargs)

Plot distributions of all numeric prediction columns in a multi-panel layout.

Args:
columns: Specific columns to plot. If None, auto-detects all numeric

columns except sequence_id, length, and hash.

**kwargs: Forwarded to matplotlib (e.g. figsize, bins).

plot_scatter(x_col: str, y_col: str, **kwargs)

Plot scatter plot.

plot_temperature_scan(metric_col: str, **kwargs)

Plot temperature scan results.

biolm.pipeline.visualization.plot_correlation_matrix(df: DataFrame, columns: list[str] | None = None, figsize: tuple[int, int] = (10, 8), save_path: Path | None = None)

Plot correlation matrix heatmap.

Args:

df: DataFrame columns: Optional list of columns to include (defaults to all numeric) figsize: Figure size save_path: Optional path to save figure

Example:
default
>>> plot_correlation_matrix(df, columns=['tm', 'plddt', 'solubility'])
biolm.pipeline.visualization.plot_distribution(df: DataFrame, column: str, bins: int = 50, figsize: tuple[int, int] = (10, 6), title: str | None = None, xlabel: str | None = None, save_path: Path | None = None)

Plot distribution of a column.

Args:

df: DataFrame column: Column name to plot bins: Number of bins figsize: Figure size title: Optional title xlabel: Optional x-axis label save_path: Optional path to save figure

Example:
default
>>> plot_distribution(df, 'tm', title='Tm Distribution')
biolm.pipeline.visualization.plot_embedding_pca(embeddings: ndarray, labels: ndarray | None = None, n_components: int = 2, figsize: tuple[int, int] = (10, 8), title: str = 'PCA of Embeddings', save_path: Path | None = None)

Plot PCA of embeddings.

Args:

embeddings: Array of embeddings (n_samples, n_features) labels: Optional array of labels for coloring n_components: Number of PCA components (2 or 3) figsize: Figure size title: Plot title save_path: Optional path to save figure

Example:
default
>>> embeddings = np.array([...])  # Load from datastore
>>> plot_embedding_pca(embeddings, labels=df['temperature'])
biolm.pipeline.visualization.plot_embedding_umap(embeddings: ndarray, labels: ndarray | None = None, n_neighbors: int = 15, min_dist: float = 0.1, figsize: tuple[int, int] = (10, 8), title: str = 'UMAP of Embeddings', save_path: Path | None = None)

Plot UMAP of embeddings.

Args:

embeddings: Array of embeddings (n_samples, n_features) labels: Optional array of labels for coloring n_neighbors: UMAP n_neighbors parameter min_dist: UMAP min_dist parameter figsize: Figure size title: Plot title save_path: Optional path to save figure

Example:
default
>>> embeddings = np.array([...])  # Load from datastore
>>> plot_embedding_umap(embeddings, labels=df['temperature'])
biolm.pipeline.visualization.plot_pipeline_funnel(stage_results: dict[str, Any], figsize: tuple[int, int] = (10, 6), save_path: Path | None = None)

Plot pipeline funnel showing sequence counts through stages.

Args:

stage_results: Dict mapping stage names to StageResult objects figsize: Figure size save_path: Optional path to save figure

Example:
default
>>> plot_pipeline_funnel(pipeline.stage_results)
biolm.pipeline.visualization.plot_scatter(df: DataFrame, x_col: str, y_col: str, color_col: str | None = None, figsize: tuple[int, int] = (10, 8), title: str | None = None, save_path: Path | None = None, alpha: float = 0.6)

Plot scatter plot of two columns.

Args:

df: DataFrame x_col: X-axis column y_col: Y-axis column color_col: Optional column for coloring points figsize: Figure size title: Optional title save_path: Optional path to save figure alpha: Point transparency

Example:
default
>>> plot_scatter(df, 'tm', 'plddt', color_col='temperature')
biolm.pipeline.visualization.plot_sequence_diversity(df: DataFrame, reference_sequence: str | None = None, figsize: tuple[int, int] = (12, 5), save_path: Path | None = None)

Plot sequence diversity metrics.

Args:

df: DataFrame with ‘sequence’ column reference_sequence: Optional reference sequence for Hamming distance figsize: Figure size save_path: Optional path to save figure

Example:
default
>>> plot_sequence_diversity(df, reference_sequence='MKTAYIAKQRQ')
biolm.pipeline.visualization.plot_temperature_scan(df: DataFrame, metric_col: str, temperature_col: str = 'temperature', figsize: tuple[int, int] = (10, 6), save_path: Path | None = None)

Plot results of temperature scanning.

Args:

df: DataFrame with temperature and metric columns metric_col: Column with metric to plot temperature_col: Column with temperature values figsize: Figure size save_path: Optional path to save figure

Example:
default
>>> plot_temperature_scan(df, 'tm', temperature_col='temperature')

Module contents

BioLM Pipeline System

A comprehensive pipeline framework for biological sequence generation, prediction, and analysis.

Requires optional dependencies — install with:

default
pip install "biolm-sdk[pipeline]"
class biolm.pipeline.BasePipeline(datastore: DuckDBDataStore | str | Path | None = None, run_id: str | None = None, output_dir: str | Path = 'pipeline_outputs', resume: bool = False, verbose: bool = True, input_schema: InputSchema | None = None)

Bases: ABC

Base class for all pipeline types.

Provides: - Stage management and dependency resolution - Async execution with progress tracking - Caching and resumability - Export and visualization

When no datastore is provided, the pipeline automatically creates a DuckDB cache under .biolm/pipelines//. The pipeline_id (and full cache path) is exposed via metadata so users can reconnect to the same cache in later sessions.

Args:

datastore: DataStore instance or path to a DuckDB file. Required. run_id: Unique run identifier (auto-generated if not provided). output_dir: Directory for CSV/Parquet exports (default pipeline_outputs). resume: Whether to resume from a previous run. verbose: Enable verbose output.

add_stage(stage: Stage)

Add a stage to the pipeline.

close()

Close the pipeline’s datastore connection.

Safe to call multiple times. Called automatically by __exit__ and __aexit__. Also called by __del__ for auto-created datastores only (user-provided datastores are not closed on GC so the caller can continue using them after the pipeline is discarded).

export_to_csv(output_path: str | Path | None = None)

Export final results to CSV.

classmethod from_db(db_path: str | Path, definition_id: str | None = None, run_id: str | None = None, verbose: bool = True) BasePipeline

Reconstruct a pipeline from an existing DuckDB database.

Useful for recovering after a kernel death without re-running already-completed stages.

Args:

db_path: Path to the DuckDB database file. definition_id: Specific definition to load (None = latest). run_id: Run ID for the reconstructed pipeline (None = generate new). verbose: Enable verbose output.

Returns:

Reconstructed BasePipeline subclass instance.

Example:

default
pipeline = DataPipeline.from_db("my_pipeline.duckdb")
pipeline.run(resume=True)
get_final_data() DataFrame

Get the final output DataFrame.

Materializes from the merged final WorkingSet via DuckDB. In a branched DAG the last-added stage is not necessarily the last executed sink, so we prefer _final_ws (set at end of run()).

The returned DataFrame always contains at minimum:

  • sequence_id, sequence, length, hash

  • source_label — label from the generation config; None when unset. Always present regardless of whether any labels were set.

  • One column per prediction type from upstream prediction stages.

property metadata: PipelineMetadata

Return metadata for reconnecting to this pipeline’s cache later.

query(sql: str, params=None) DataFrame

Execute arbitrary SQL against the pipeline’s DuckDB datastore.

results() DataFrame

Return the final output DataFrame. Alias for get_final_data().

The returned DataFrame always contains at minimum:

  • sequence_id, sequence, length, hash

  • source_label — the label set on the generation config (e.g. DirectGenerationConfig.label). None when no label was set. Always present — do not guard on "source_label" in df.columns.

  • One column per prediction type computed by any prediction stages.

run(enable_streaming: bool = True, **kwargs) dict[str, biolm.pipeline.base.StageResult]

Run the pipeline synchronously.

This is a convenience wrapper around run_async(). Works in both script/notebook environments (detects running event loops).

async run_async(enable_streaming: bool = True, **kwargs) dict[str, biolm.pipeline.base.StageResult]

Run the pipeline asynchronously.

Args:
enable_streaming: Stream prediction results through per-sequence

filters for better parallelism and lower latency (default True).

Returns:

Dict mapping stage names to StageResults

summary() DataFrame

Get pipeline summary statistics.

class biolm.pipeline.ClusteringResult(cluster_ids: ndarray, centroids: list[str], centroid_indices: ndarray, n_clusters: int, silhouette_score: float | None = None, davies_bouldin_score: float | None = None, cluster_sizes: dict[int, int] | None = None)

Bases: object

Results from sequence clustering.

centroid_indices: ndarray
centroids: list[str]
cluster_ids: ndarray
cluster_sizes: dict[int, int] | None = None
davies_bouldin_score: float | None = None
n_clusters: int
silhouette_score: float | None = None
class biolm.pipeline.CofoldingPredictionStage(name: str, model_name: str, action: str = 'predict', prediction_type: str = 'structure', sequence_chain_id: str = 'A', sequence_entity_type: str = 'protein', static_entities=None, params: dict | None = None, batch_size: int = 1, item_columns: dict[str, str] | None = None, **kwargs)

Bases: Stage

Prediction stage for co-folding models (Boltz2, Chai-1).

Each sequence in the pipeline DataFrame is used as the primary entity (chain) in a multi-molecule folding request. Additional static entities — ligands, cofactors, DNA/RNA, or other protein chains — are injected via static_entities and held constant for every sequence.

The caller is responsible for providing the correct molecule field names and params for the target model. Boltz2 and Chai-1 both expect an item shaped like {'molecules': [{'id': ..., 'type': ..., 'sequence': ...}, ...]}.

Args:

name: Stage name. model_name: BioLM model slug ('boltz2', 'chai1'). action: API action (default 'predict'). prediction_type: Column name written for the confidence score

(default 'structure').

sequence_chain_id: Chain ID / name assigned to the pipeline’s primary

sequence in the molecules list.

sequence_entity_type: Molecule type for the primary sequence

('protein', 'dna', 'rna').

static_entities: List of FoldingEntity

objects appended to every molecules list after the primary chain.

params: Model-specific params dict passed directly to the API

(e.g. {'recycling_steps': 3, 'sampling_steps': 20} for Boltz).

batch_size: Sequences per API call (default 1; co-folding models are

typically limited to batch size 1).

Example:

default
from biolm.pipeline import FoldingEntity

pipeline.add_cofolding_prediction(
    model_name='boltz2',
    static_entities=[
        FoldingEntity(id='L', entity_type='ligand', smiles='c1ccccc1'),
    ],
    params={'recycling_steps': 3, 'sampling_steps': 20},
    depends_on=['filter_top50'],
)
merge_mode: str = 'union'
async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]

Run co-folding prediction for each sequence in df.

async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]

Run co-folding prediction using WorkingSet — no DataFrame transport.

to_spec() dict

Serialize to a dict for pipeline definition persistence.

Note: static_entities (FoldingEntity objects) are not serializable and are omitted. A reconstructed pipeline will not have static_entities and must have them re-attached manually after from_db().

class biolm.pipeline.CompositeFilter(*filters: BaseFilter)

Bases: BaseFilter

A serializable filter that chains sub-filters sequentially.

Unlike CustomFilter, CompositeFilter is fully serializable via to_spec() / filter_from_spec() as long as every sub-filter is serializable. It also supports SQL fast-path evaluation when all sub-filters implement to_sql() and exactly one SQL filter is present.

Args:

*filters: Sub-filters applied left to right.

Example:
default
>>> combined = CompositeFilter(
...     ThresholdFilter('tm', min_value=60),
...     SequenceLengthFilter(min_length=100),
... )
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

to_sql(ws_table: str = '_filter_ws', **kwargs) str | None

Return SQL only when a single sub-filter supports it.

Full CTE-chaining for multiple SQL filters is complex to implement correctly (each stage’s output must feed the next as a temporary table). For now we support the single-SQL-filter case and fall back to DataFrame materialization for all other combinations.

class biolm.pipeline.ConservedResidueFilter(conserved_positions: dict[int, list[str]], reference_length: int | None = None)

Bases: BaseFilter

Filter sequences that have specific residues at specific positions.

Args:
conserved_positions: Dict mapping position (0-indexed) to allowed residues

e.g., {5: [‘M’, ‘L’], 10: [‘K’]}

reference_length: Expected sequence length (optional)

Example:
default
>>> filter = ConservedResidueFilter({107: ['H'], 109: ['H'], 126: ['H']})
>>> df_filtered = filter(df)
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

class biolm.pipeline.CustomFilter(func: Callable[[DataFrame], DataFrame], name: str | None = None)

Bases: BaseFilter

Apply a custom filter function.

Args:

func: Function that takes a DataFrame and returns a filtered DataFrame name: Optional name for the filter (for repr)

Example:
default
>>> def my_filter(df):
...     return df[df['sequence'].str.contains('M')]
>>> filter = CustomFilter(my_filter, name='contains_M')
>>> df_filtered = filter(df)
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

class biolm.pipeline.DataPipeline(sequences: list[str] | DataFrame | str | Path = None, diff_mode: bool = False, input_columns: list[str] | None = None, **kwargs)

Bases: BasePipeline

Pipeline for processing existing sequences from files or lists.

Load sequences from CSV/FASTA/lists and run predictions/filtering.

Args:

sequences: Input sequences (list of strings, DataFrame, or file path) datastore: DataStore instance or path run_id: Unique run ID output_dir: Output directory resume: Resume from previous run verbose: Enable verbose output diff_mode: If True, merge new sequences with existing cached results.

Only computes predictions for uncached sequences. Use get_merged_results() or query_results() to efficiently access combined data without loading millions of rows into memory (SQL-based queries).

Example:
default
>>> # Standard mode
>>> pipeline = DataPipeline(sequences='sequences.csv')
>>> pipeline.add_prediction('esmfold', extractions='mean_plddt', columns='plddt')
>>> pipeline.add_filter(ThresholdFilter('plddt', min_value=70))
>>> results = pipeline.run()
default
>>> # Diff mode - add new sequences to existing pipeline (SQL-based, efficient)
>>> pipeline = DataPipeline(sequences='new_sequences.csv', diff_mode=True)
>>> pipeline.add_prediction('esmfold', extractions='mean_plddt', columns='plddt')
>>> results = pipeline.run()
>>> # Efficiently query specific data (doesn't load all millions of rows!)
>>> high_quality = pipeline.query_results("s.length > 100 AND p.value > 70")
>>> # Or get merged results with filters
>>> merged = pipeline.get_merged_results(prediction_types=['plddt', 'tm'])
add_clustering(method: str = 'kmeans', n_clusters: int | None = None, similarity_metric: str = 'hamming', embedding_model: str | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)

Add a sequence clustering stage.

Clusters sequences by similarity and adds cluster_id column to DataFrame.

Args:

method: Clustering algorithm (‘kmeans’, ‘dbscan’, ‘hierarchical’) n_clusters: Number of clusters (required for kmeans/hierarchical) similarity_metric: ‘hamming’ or ‘embedding’ embedding_model: Model name if using embedding similarity stage_name: Optional custom stage name depends_on: Optional list of stage names this stage depends on **kwargs: Additional arguments for clustering (eps, min_samples, etc.)

Example:
default
>>> # Cluster by sequence similarity
>>> pipeline.add_clustering(method='kmeans', n_clusters=10)
>>>
>>> # Cluster by embeddings
>>> pipeline.add_prediction('esm2-650m', action='encode', stage_name='embed')
>>> pipeline.add_clustering(
...     method='kmeans',
...     n_clusters=5,
...     similarity_metric='embedding',
...     embedding_model='esm2-650m',
...     depends_on=['embed']
... )
add_cofolding_prediction(model_name: str, action: str = 'predict', stage_name: str | None = None, prediction_type: str = 'structure', sequence_chain_id: str = 'A', sequence_entity_type: str = 'protein', static_entities=None, depends_on: list[str] | None = None, params: dict | None = None, batch_size: int = 1)

Add a co-folding prediction stage (Boltz2, Chai-1).

Each pipeline sequence becomes the primary entity in a multi-molecule folding request. static_entities injects ligands, cofactors, DNA/RNA strands, or additional protein chains that are constant across all designs.

The caller is responsible for providing the correct molecule field names via static_entities and the right params for the model.

Args:

model_name: BioLM model slug ('boltz2', 'chai1'). action: API action (default 'predict'). stage_name: Optional stage name (defaults to model_name). prediction_type: Column name for the confidence score. sequence_chain_id: Chain ID assigned to the primary sequence

(e.g. 'A' for Boltz, molecule name for Chai-1).

sequence_entity_type: Entity type for the primary sequence

('protein', 'dna', 'rna').

static_entities: List of FoldingEntity objects to include

in every request alongside the primary sequence.

depends_on: Upstream stage names. params: Model-specific params (e.g. {'recycling_steps': 3}). batch_size: Sequences per API call (default 1).

Example:

default
from biolm.pipeline import FoldingEntity

pipeline.add_cofolding_prediction(
    model_name='boltz2',
    static_entities=[
        FoldingEntity(id='L', entity_type='ligand', smiles='c1ccccc1'),
    ],
    params={'recycling_steps': 3, 'sampling_steps': 20},
    depends_on=['filter_top50'],
)
add_filter(filter_func: BaseFilter | Callable[[...], Any], stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)

Add a filter stage.

Args:

filter_func: Filter function or BaseFilter instance stage_name: Custom stage name depends_on: List of stage names this depends on. When None

(default), auto-depends on the last added stage. Pass depends_on=[] for earliest-level execution.

add_prediction(model_name: str, action: str = 'predict', extractions: str | list[Union[str, biolm.pipeline.data.ExtractionSpec]] | None = None, columns: str | dict[str, str] | None = None, params: dict | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)

Add a prediction stage.

Args:

model_name: BioLM model name action: API action (‘predict’, ‘encode’, ‘score’) extractions: API response key(s) to extract. Required for

predict/score actions. Can be a string for a single key or a list of strings / ExtractionSpec objects.

columns: Output column name(s). A string renames a single

extraction; a dict maps response keys to column names (unmapped keys keep their name).

params: Optional API parameters stage_name: Custom stage name (defaults to predict_{first_column}) depends_on: List of stage names this depends on. When None

(default), the stage auto-depends on the last added stage, creating a sequential chain. Pass depends_on=[] to run at the earliest possible level (parallel with other level-0 stages).

Example:

default
pipeline.add_prediction(
    "temberture-regression",
    extractions="prediction",
    columns="tm",
)
add_predictions(models: list[Union[str, dict]], action: str = 'predict', depends_on: list[str] | None = None, **kwargs)

Add multiple prediction stages at the same level (run in parallel).

Args:

models: List of model names or dicts with model configs action: Default action if not specified in dict depends_on: List of stage names all these depend on **kwargs: Default kwargs for all stages

Returns:

self for chaining

Example:
default
>>> pipeline.add_predictions([
...     {'model_name': 'temberture-regression', 'extractions': 'prediction', 'columns': 'tm'},
...     {'model_name': 'biolmsol', 'extractions': 'solubility_score', 'columns': 'solubility'},
... ])
add_structure_prediction(model_name: str, structure_key: str = 'pdb', extractions: str | list[Union[str, biolm.pipeline.data.ExtractionSpec]] | None = None, columns: str | dict[str, str] | None = None, plddt_key: str | None = None, structure_format: str | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)

Add a prediction stage that stores the structure from the response.

Convenience wrapper around add_prediction() with a StructureSpec.

Args:

model_name: BioLM model name (e.g. ‘esmfold’, ‘alphafold2’). structure_key: Response key containing the structure string. extractions: Optional scalar extractions (e.g. ‘mean_plddt’). columns: Output column name(s) for scalar extractions. plddt_key: Optional response key for confidence score. structure_format: ‘pdb’ or ‘cif’ (auto-detected from key if None). stage_name: Custom stage name. depends_on: Stage dependencies.

Example:

default
pipeline.add_structure_prediction(
    "esmfold",
    extractions="mean_plddt", columns="plddt",
    plddt_key="mean_plddt",
)
explore() dict[str, Any]

Return summary stats for the pipeline’s datastore (all via SQL).

Returns:

Dict with keys: sequences, embeddings, generated, completed_stages, predictions (dict of prediction_type → count).

get_merged_results(prediction_types: list[str] | None = None, sequence_filter: str | None = None) DataFrame

Get results merged with existing cached data (for diff mode).

This method is SQL-based and efficient - it doesn’t load millions of rows. Instead, it queries only the data you need using DuckDB’s columnar engine.

NOTE (Bug #5): sequence_filter and sql_where in query_results() are interpolated directly into SQL. These parameters are intended for internal/ trusted caller use only — never pass untrusted user input to them.

Args:

prediction_types: List of prediction types to include (None = all) sequence_filter: SQL WHERE clause to filter sequences (e.g., “length > 50”).

TRUSTED CALLERS ONLY — not safe for untrusted user input.

Returns:

DataFrame with requested sequences and predictions

Example:
default
>>> # Get all results (efficient - DuckDB only loads what's needed)
>>> df = pipeline.get_merged_results()
default
>>> # Get only specific predictions (columnar - even faster!)
>>> df = pipeline.get_merged_results(prediction_types=['tm', 'plddt'])
default
>>> # Get sequences matching criteria (predicate pushdown!)
>>> df = pipeline.get_merged_results(sequence_filter="length > 100")
plot(kind: str = 'funnel', **kwargs)

Convenience wrapper around PipelinePlotter.

Args:
kind: One of ‘funnel’, ‘predictions’, ‘distributions’, ‘scatter’,

‘correlation’, ‘diversity’, ‘temperature’.

**kwargs: Forwarded to the underlying plotter method.

scatter requires x_col and y_col. diversity accepts reference_sequence. temperature requires metric_col.

query(sql: str, params=None) DataFrame

Execute arbitrary SQL against the pipeline’s DuckDB datastore.

Args:

sql: DuckDB SQL query string. params: Optional list of query parameters.

Returns:

DataFrame with results.

Example:
default
>>> pipeline.query("SELECT * FROM sequences WHERE length > 100")
query_results(sql_where: str, columns: list[str] | None = None) DataFrame

Query results using SQL WHERE clause (for diff mode with large datasets).

This leverages DuckDB’s vectorized engine for maximum performance.

NOTE (Bug #5): sql_where is interpolated directly into SQL. TRUSTED CALLERS ONLY — never pass untrusted user input to this parameter.

Args:
sql_where: SQL WHERE clause using table aliases:
  • s.* for sequences table (e.g., “s.length > 100”)

  • Column names directly (no p. prefix needed)

TRUSTED CALLERS ONLY — not safe for untrusted user input.

columns: Columns to include (None = all available)

Returns:

DataFrame with matching sequences (only loads what matches!)

Example:
default
>>> # Find long sequences (columnar scan - fast!)
>>> df = pipeline.query_results("s.length > 200")
default
>>> # Complex filter with predictions
>>> df = pipeline.query_results(
...     "s.length > 100",
...     columns=['tm', 'plddt']
... )
stats(stage_name: str | None = None) DataFrame

Return per-stage counts from stage_completions.

Args:

stage_name: If provided, filter to that stage only.

Returns:

DataFrame with columns: stage_name, status, input_count, output_count, completed_at.

biolm.pipeline.DataStore

alias of DuckDBDataStore

class biolm.pipeline.DirectGenerationConfig(model_name: str, structure_path: str | None = None, structure_column: str | None = None, sequence: str | None = None, item_field: str = 'pdb', params: dict[str, typing.Any] = <factory>, num_sequences: int = 100, temperature: float = 1.0, structure_from_stage: str | None = None, structure_from_model: str | None = None, n_runs: int = 1, label: str | None = None)

Bases: GenerativeProtocolConfig

Configuration for structure- or sequence-conditioned generation.

Use with models such as ProteinMPNN, AntiFold, HyperMPNN, LigandMPNN, DSM.

The caller is responsible for providing the correct item_field and params for the target model — these vary per model and are documented in the BioLM API schema (/schema//generate/). Common values:

Args:
model_name: BioLM model slug (e.g. 'protein-mpnn', 'antifold',

'dsm-150m-base').

structure_path: Path to a PDB or CIF file. structure_column: DataFrame column holding PDB strings (for chained

pipelines where structure was predicted upstream).

sequence: Parent sequence string for sequence-conditioned models (DSM). item_field: The item dict key expected by the model API — 'pdb' for

structure-conditioned models, 'sequence' for DSM. Defaults to 'pdb'.

params: Model-specific params dict (required for non-trivial calls).

Keys must exactly match the model’s API param names. When empty the stage sends {'num_sequences': num_sequences, 'temperature': temperature} as a simple fallback — useful only for models that accept these exact param names.

num_sequences: Fallback when params is empty (default 100). temperature: Fallback when params is empty (default 1.0).

Example:

default
from biolm.pipeline.generative import DirectGenerationConfig, GenerativePipeline

# Structure-conditioned design with ProteinMPNN
cfg_mpnn = DirectGenerationConfig(
    model_name="protein-mpnn",
    structure_path="/path/to/protein.pdb",
    item_field="pdb",
    params={"num_sequences": 50, "temperature": 0.1},
)

# Sequence-conditioned design with DSM
cfg_dsm = DirectGenerationConfig(
    model_name="dsm-150m-base",
    sequence="MKTAYIAKQRQ",
    item_field="sequence",
    params={
        "num_sequences": 100,
        "temperature": 1.0,
        "remasking": "low_confidence",
        "step_divisor": 8,
    },
)

pipeline = GenerativePipeline(configs=[cfg_mpnn])
results = pipeline.run()
item_field: str = 'pdb'
label: str | None = None
model_name: str
n_runs: int = 1
num_sequences: int = 100
params: dict[str, Any]
sequence: str | None = None
structure_column: str | None = None
structure_from_model: str | None = None
structure_from_stage: str | None = None
structure_path: str | None = None
temperature: float = 1.0
to_spec() dict

Return a serializable dict for pipeline definition persistence.

class biolm.pipeline.DiversityAnalyzer

Bases: object

Analyze sequence diversity and coverage.

Provides metrics for understanding sequence space exploration.

Performance Notes:
  • Shannon entropy: O(n*L) where L is sequence length

  • Pairwise distances: O(n²*L) - use max_sample for large n

  • All metrics scale linearly except pairwise distances

Example:
default
>>> analyzer = DiversityAnalyzer()
>>> metrics = analyzer.compute_all_metrics(sequences, max_sample=10000)
>>> print(f"Shannon entropy: {metrics['shannon_entropy']:.2f}")
classmethod compute_all_metrics(sequences: list[str], max_sample: int | None = 10000) dict[str, Union[float, dict]]

Compute all diversity metrics at once.

Args:

sequences: List of protein sequences max_sample: Unused, reserved for future embedding-based pairwise stats

Returns:

Dictionary with all diversity metrics

static motif_diversity(sequences: list[str], k: int = 3) dict[str, Union[int, float]]

Analyze k-mer (motif) diversity.

Args:

sequences: List of protein sequences k: Length of k-mers to analyze

Returns:

Dictionary with k-mer statistics

static shannon_entropy(sequences: list[str], normalize: bool = True) float

Calculate Shannon entropy of amino acid distribution.

Measures positional diversity across all sequences. Optimized for large datasets using vectorized operations.

Args:

sequences: List of protein sequences normalize: Normalize by log(20) for 0-1 range

Returns:

Shannon entropy (higher = more diverse)

class biolm.pipeline.DiversitySamplingFilter(n_samples: int, method: str = 'random', score_column: str | None = None, random_seed: int | None = 42, resample: bool = True)

Bases: BaseFilter

Sample diverse sequences using clustering or random sampling.

This filter REQUIRES complete data to assess diversity.

Args:

n_samples: Number of sequences to sample method: Sampling method (‘random’, ‘spread’, ‘top’) score_column: Column to use for ‘top’ method random_seed: Random seed for reproducibility resample: If False, only sample if not already sampled (default: True)

Example:
default
>>> filter = DiversitySamplingFilter(n_samples=1000, method='random')
>>> df_sampled = filter(df)
requires_complete_data: bool = True
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

class biolm.pipeline.DuckDBDataStore(db_path: str | Path | None = None, data_dir: str | Path | None = None)

Bases: object

DuckDB + Parquet based datastore for efficient sequence management.

Optimized for: - Millions of sequences - Complex queries (joins, filters, aggregations) - Out-of-core operations (bigger than RAM) - Diff mode with efficient deduplication

Args:

db_path: Path to DuckDB database file data_dir: Directory for Parquet files and large data

Example:
default
>>> ds = DuckDBDataStore("pipeline.db", "data/")
>>> seq_id = ds.add_sequence("MKLLIV")
>>> ds.add_prediction(seq_id, "tm", "temberture-regression", 65.5)
>>>
>>> # Efficient query - no memory explosion
>>> high_tm = ds.query("SELECT * FROM predictions WHERE value > 60")
add_embedding(sequence_id: int, model_name: str, embedding: ndarray, layer: int | None = None)

Add embedding stored inline in DuckDB as FLOAT[] (no per-file Parquet overhead).

Args:

sequence_id: Sequence ID model_name: Model name embedding: Numpy array layer: Optional layer number

add_embeddings_batch(data: list[dict])

Batch-insert embeddings in a single DuckDB statement.

Each dict must have: sequence_id (int), model_name (str), embedding (np.ndarray). Optional: layer (int or None).

Significantly faster than N individual add_embedding() calls — one INSERT…SELECT vs. N individual row inserts.

add_generation_metadata(sequence_id: int, model_name: str, run_id: str = '', temperature: float | None = None, top_k: int | None = None, top_p: float | None = None, num_return_sequences: int | None = None, do_sample: bool | None = None, repetition_penalty: float | None = None, max_length: int | None = None, sampling_params: dict | None = None) int

Store generation parameters for a sequence.

Returns:

metadata_id of the inserted row.

add_generation_metadata_batch(rows: list[dict]) None

Batch-insert generation metadata — one DuckDB round-trip.

Each dict in rows must have at minimum sequence_id and model_name. Optional fields: temperature, top_k, top_p, num_return_sequences, do_sample, repetition_penalty, max_length.

add_prediction(sequence_id: int, prediction_type: str, model_name: str, value: float | None, metadata: dict | None = None)

Add single prediction (convenience wrapper).

add_prediction_by_sequence(sequence: str, prediction_type: str, model_name: str, value: float | None, metadata: dict | None = None) int

Add a prediction, creating the sequence if it doesn’t exist.

Returns:

prediction_id of the inserted row.

add_predictions_batch(data: list[dict[str, Any]])

Batch add predictions efficiently.

Args:
data: List of dicts with keys: sequence_id, prediction_type,

model_name, value, metadata (optional)

Example:
default
>>> ds.add_predictions_batch([
...     {'sequence_id': 1, 'prediction_type': 'tm',
...      'model_name': 'temberture-regression', 'value': 65.5},
...     {'sequence_id': 2, 'prediction_type': 'tm',
...      'model_name': 'temberture-regression', 'value': 70.2},
... ])
add_sequence(sequence: str) int

Add single sequence (convenience wrapper).

add_sequences_batch(sequences: list[str] | None = None, deduplicate: bool = True, input_df: DataFrame | None = None, input_columns: list[str] | None = None) list[int]

Add multiple sequences efficiently using anti-join deduplication.

This is the RECOMMENDED way to add sequences - vectorized and fast!

Two calling conventions:

  1. Legacy (sequence-only): add_sequences_batch(["MKLLIV", ...])

  2. Multi-column (arbitrary input columns): add_sequences_batch(input_df=df, input_columns=["heavy_chain", "light_chain"]) In this mode, the hash is computed across all input columns, and the column values are stored directly on the sequences table. A sequence column is still written (concatenation of all input columns joined with :) so downstream code has a fallback.

Args:

sequences: List of sequence strings (legacy path). deduplicate: Use anti-join to skip existing sequences. input_df: DataFrame with input columns (multi-column path). input_columns: Column names in input_df to use as primary data.

Returns:

List of sequence_ids (new and existing), preserving input order.

add_structure(sequence_id: int, model_name: str, structure_str: str | None = None, format: str = 'pdb', plddt_mean: float | None = None, plddt: float | None = None) int

Store a structure gzip-compressed as BLOB (~8-12x smaller than plain TEXT).

Args:

sequence_id: Sequence ID. model_name: Model that produced the structure (e.g. ‘esmfold’). structure_str: Full structure file content as a string. format: ‘pdb’ or ‘cif’ (default ‘pdb’). plddt_mean: Mean pLDDT score (optional). plddt: Alias for plddt_mean.

Returns:

structure_id of the inserted row.

close()

Close the DuckDB connection and release the file lock.

Safe to call multiple times — subsequent calls are no-ops. Called automatically by __exit__ and __del__.

count_matching_sequences(sequences: list[str]) int

Count how many of the given sequences already exist in the datastore.

Uses a single vectorized hash join instead of N individual lookups. Safe to call from any context — registers DataFrame explicitly.

create_pipeline_run(run_id: str, pipeline_type: str, config: dict, status: str = 'running')

Create or update a pipeline run record (safe for resume runs).

ensure_input_columns(columns: list[str])

Ensure the sequences table has all the given columns.

Uses ALTER TABLE ADD COLUMN for any that don’t already exist. This is idempotent — safe to call on every pipeline run.

Raises:
duckdb.CatalogException: If column creation fails for an unexpected

reason (e.g. type conflict). The “column already exists” case is silently ignored as it is the normal idempotent path.

execute_filter_sql(sequence_ids: list[int], sql_query: str, ws_table_name: str = '_filter_ws') list[int]

Execute a filter SQL query and return surviving sequence IDs.

The sql_query must be a complete SELECT statement that returns sequence_id values and JOINs against ws_table_name (which contains the input sequence_ids) to scope to the working set.

Pass a unique ws_table_name (use make_filter_ws_name()) when running multiple SQL-native filters concurrently — the default _filter_ws name races otherwise.

Args:

sequence_ids: Input sequence IDs. sql_query: Complete SQL SELECT returning sequence_id values. ws_table_name: Name to register the working-set IDs under. Must

match the ws_table= argument passed to the filter’s to_sql().

Returns:

List of sequence_ids that survive the filter.

Raises:

ValueError: If sql_query is not a single SELECT statement.

export_to_csv(path: str | Path, **kwargs) None

Export all data to CSV (convenience wrapper around export_to_dataframe).

export_to_dataframe(include_sequences: bool = True, include_predictions: bool = True, include_generation_metadata: bool = False, prediction_types: list[str] | None = None, run_id: str | None = None) DataFrame

Export data to a flat DataFrame using a single DuckDB SQL query.

Uses conditional aggregation (CASE WHEN pivot) — no per-type queries, no pandas merges, no full table loads.

Args:

include_sequences: Always True; includes sequence_id, sequence, length. include_predictions: Pivot prediction_type values into columns. include_generation_metadata: Join generation_metadata columns. prediction_types: Limit to specific prediction types (None = all).

Returns:

Wide-format DataFrame: one row per sequence, one column per prediction type.

export_to_parquet(table_name: str, output_path: str | Path)

Export table to Parquet file (for sharing/archiving).

Args:

table_name: Table to export (sequences, predictions, etc.) output_path: Path to output Parquet file

Example:
default
>>> ds.export_to_parquet('sequences', 'sequences_backup.parquet')
get_all_sequences() DataFrame

Return all sequences as a DataFrame with sequence_id, sequence, length columns.

get_column_registry_entry(column_name: str) dict | None

Return the registry entry for a column, or None if not registered.

get_context(run_id: str, key: str) Any | None

Retrieve a value from the pipeline context table.

get_embedding(embedding_id: int) tuple | None

Return (metadata_dict, embedding_array) for a given embedding_id, or None.

get_embeddings_bulk(sequence_ids: list[int], model_name: str | None = None) dict[int, numpy.ndarray]

Fetch embeddings for multiple sequences in a single JOIN query.

Replaces N individual get_embeddings_by_sequence() calls (O(n) queries → O(1)).

Args:

sequence_ids: List of sequence IDs to fetch. model_name: Optional model filter.

Returns:

Dict mapping sequence_id → numpy embedding array for sequences that have an embedding.

get_embeddings_by_sequence(sequence: str, model_name: str | None = None, load_data: bool = False) list[dict]

Get embeddings for a sequence.

get_embeddings_concat(sequence_ids: list[int], model_names: list[str]) dict[int, numpy.ndarray]

Fetch and concatenate embeddings from multiple models per sequence.

For each sequence_id, retrieves the embedding from each model in model_names order and horizontally concatenates them into a single vector. Sequences missing an embedding from any requested model are omitted from the result.

Args:

sequence_ids: Sequence IDs to fetch. model_names: Ordered list of model names whose embeddings will be

concatenated (e.g. ["esm2-8m", "esmc-300m"]).

Returns:

Dict mapping sequence_id → concatenated numpy array.

get_existing_input_columns() list[str]

Return extra columns on the sequences table (beyond the base schema).

Used for input-schema validation when connecting to an existing DB. Returns an empty list if only the base columns are present.

get_filter_results(run_id: str, stage_name: str) list[int]

Return sequence_ids that passed a given filter stage in a run.

Args:

run_id: Pipeline run ID. stage_name: Filter stage name.

Returns:

List of sequence_ids that passed the filter, or empty list if no data.

get_generation_metadata(sequence_id: int) list[dict]

Return generation metadata records for a sequence_id.

get_latest_definition_id() str | None

Return the definition_id of the most recently created pipeline definition.

get_latest_run_id(definition_id: str | None = None) str | None

Return the run_id of the most recent pipeline run.

from_db() must reuse the existing run_id so that resume can find already-completed stages. A new run_id means no stages are marked complete, causing everything to re-run.

Args:
definition_id: If provided, restrict to runs that used this definition.

If None, return the most recent run across all definitions.

Returns:

The run_id string, or None if no runs exist.

get_pipeline_metadata(key: str) Any | None

Retrieve a value from pipeline_metadata by key.

get_pipeline_run(run_id: str) dict | None

Return pipeline run record as a dict, or None if not found.

get_predictions(sequence_id: int, prediction_type: str | None = None, model_name: str | None = None) DataFrame

Return predictions for a sequence_id as a DataFrame.

get_predictions_bulk(sequence_ids: list[int], prediction_type: str, model_name: str) DataFrame

Fetch predictions for multiple sequences in a single JOIN query.

Replaces N individual get_predictions_by_sequence() calls.

Returns:

DataFrame with columns: sequence_id, value, metadata

get_predictions_by_sequence(sequence: str, prediction_type: str | None = None, model_name: str | None = None) DataFrame

Get predictions for a sequence.

get_sequence(sequence_id: int) str | None

Return the sequence string for a given sequence_id, or None if not found.

get_sequence_attributes_for_ids(sequence_ids: list[int], attr_names: list[str]) dict[int, dict[str, str]]

Retrieve per-sequence attributes, returning {seq_id: {attr: value}}.

Args:

sequence_ids: Sequence IDs to look up. attr_names: Attribute names to retrieve.

Returns:

Nested dict: {sequence_id: {attr_name: attr_value}}.

get_sequence_id(sequence: str) int | None

Get sequence_id for a sequence.

get_sequence_ids_with_prediction(sequence_ids: list[int], prediction_type: str, model_name: str) list[int]

Return sequence_ids that DO have a given prediction (inverse of uncached check).

Args:

sequence_ids: Candidate sequence IDs. prediction_type: Prediction type key. model_name: Model name.

Returns:

List of sequence_ids that have a cached prediction.

get_sequences_for_ids(sequence_ids: list[int]) list[tuple[int, str]]

Fetch (sequence_id, sequence) pairs for the given IDs.

Lightweight fetch for building API request items without materializing a full DataFrame.

Args:

sequence_ids: List of sequence IDs to look up.

Returns:

List of (sequence_id, sequence_string) tuples.

get_sequences_for_ids_with_columns(sequence_ids: list[int], columns: list[str]) dict[int, dict[str, str]]

Fetch column values from the sequences table for given IDs.

This reads columns stored directly on the sequences table (via ensure_input_columns), NOT from sequence_attributes.

Args:

sequence_ids: Sequence IDs to look up. columns: Column names to fetch (must exist on the sequences table).

Returns:

{sequence_id: {col: value, ...}}

get_stats() dict[str, int]

Return row counts for the main tables.

get_structure(sequence_id: int, model_name: str | None = None) dict | None

Fetch the most recent structure for a sequence, decompressing on read.

Returns a dict with ‘structure_str’ key (always decompressed string) regardless of whether data was stored compressed (structure_data BLOB) or as legacy plain TEXT.

Args:

sequence_id: Sequence ID. model_name: Optional model filter.

Returns:

Dict with structure record, or None if not found.

get_structure_by_id(structure_id: int) dict | None

Return a structure record by its structure_id (primary key).

get_structures_bulk(sequence_ids: list[int]) DataFrame

Fetch structures for multiple sequences, decompressing structure content.

Returns a DataFrame with a ‘structure_str’ column (always plain text) regardless of whether data was stored compressed (structure_data BLOB) or as legacy plain TEXT.

Args:

sequence_ids: List of sequence IDs to look up.

Returns:

DataFrame with one row per structure record.

get_structures_by_sequence(sequence: str, model_name: str | None = None) list[dict]

Return structure records for a sequence string (decompressed).

get_structures_for_ids(sequence_ids: list[int], model_name: str) dict[int, dict]

Fetch the most recent structure for each sequence_id in one query.

Returns {sequence_id: record} where record has a ‘structure_str’ key. Replaces N individual get_structure() calls for batch structure injection.

get_uncached_sequence_ids(sequence_ids: list[int], prediction_type: str, model_name: str) list[int]

Return sequence_ids that do NOT yet have a given prediction (vectorized anti-join).

Replaces N individual has_prediction() calls with a single SQL query.

Args:

sequence_ids: Candidate sequence IDs to check. prediction_type: Prediction type key. model_name: Model name.

Returns:

List of sequence_ids with no cached prediction.

has_prediction(sequence: str, prediction_type: str, model_name: str) bool

Check if prediction exists for sequence.

is_stage_complete(stage_id: str) bool

Check if stage is complete.

load_blob(blob_id: str) str | None

Retrieve a stored blob by its blob_id. Returns None if not found.

load_pipeline_definition(definition_id: str | None = None) dict | None

Load a pipeline definition by ID, or the latest one if ID is None.

make_filter_ws_name() str

Mint a unique _filter_ws_ table name for one filter execution.

Required when SQL-native filters can run in parallel at the same DAG level — sharing the canonical _filter_ws registration would let two stages clobber each other’s working set.

mark_stage_complete(run_id: str, stage_name: str, stage_id: str, input_count: int, output_count: int, status: str = 'completed')

Mark stage as complete (or failed/skipped).

uses INSERT … ON CONFLICT DO NOTHING so that a stage that was already marked ‘completed’ does not get its completed_at timestamp overwritten on resume. Only truly new rows are inserted.

materialize_working_set(ws: WorkingSet, include_predictions: bool = True, prediction_types: list[str] | None = None) pd.DataFrame

Materialize a WorkingSet into a DataFrame via a single DuckDB pivot query.

Args:

ws: WorkingSet containing the sequence IDs to materialize. include_predictions: If True, pivot prediction values into columns. prediction_types: Limit to specific types (None = all available).

Returns:

Wide-format DataFrame: one row per sequence, one column per prediction type. Always includes the following columns regardless of pipeline configuration:

  • sequence_id, sequence, length, hash — core sequence data.

  • source_label — label set on the generation config (e.g. DirectGenerationConfig.label, SaturationMutagenesisConfig.label). NULL / None when no label was supplied. This column is always present even when no labels have been set, so downstream code should not branch on "source_label" in df.columns — it is always there.

query(sql: str, params: list | None = None) DataFrame

Execute arbitrary SQL query and return DataFrame.

This is the POWER feature - query directly without loading everything!

Args:

sql: DuckDB SQL query params: Optional query parameters

Returns:

DataFrame with results (only loads what matches query!)

Example:
default
>>> # Find high-quality long sequences
>>> df = ds.query('''
...     SELECT s.sequence, p.value as plddt
...     FROM sequences s
...     JOIN predictions p ON s.sequence_id = p.sequence_id
...     WHERE s.length > 200
...     AND p.prediction_type = 'plddt'
...     AND p.value > 80
... ''')
register_column(column_name: str, model_name: str, action: str, definition_id: str, stage_name: str)

Register an output column in the prediction_column_registry. Idempotent.

save_filter_results(run_id: str, stage_name: str, passed_sequence_ids: list[int])

Record which sequence_ids passed a filter stage (for resume support).

Args:

run_id: Pipeline run ID. stage_name: Filter stage name. passed_sequence_ids: IDs of sequences that passed the filter.

save_pipeline_definition(definition_id: str, pipeline_type: str, input_schema_json: str | None, stages_json: str)

Persist a pipeline definition. Updates stages/schema if definition_id already exists.

set_context(run_id: str, key: str, value: Any)

Store a key-value pair in the pipeline context table.

set_pipeline_metadata(key: str, value: Any)

Upsert a key/value pair in the pipeline_metadata table.

store_blob(content: str) str

Store a large string value, returning its blob_id (SHA-256[:32]).

Content-addressed: identical content always maps to the same blob_id. Safe to call multiple times with the same content (INSERT OR IGNORE).

store_sequence_attributes(seq_ids: list[int], attr_name: str, attr_values: list[str])

Persist a per-sequence attribute column (e.g. heavy_chain, light_chain).

Args:

seq_ids: Sequence IDs. attr_name: Attribute name (column name from the input DataFrame). attr_values: Corresponding values (one per sequence_id).

update_pipeline_run_status(run_id: str, status: str)

Update pipeline run status.

biolm.pipeline.Embed(model_name: str, sequences: list[str] | DataFrame | str | Path, layer: int | None = None, key: str | None = None, **kwargs) DataFrame

Convenience function for generating embeddings.

Args:
model_name: BioLM model name (e.g., 'esm2-8m', 'esm2-650m',

'ablang2'). See biolm.list_models() for the full slugged list — the family name alone ('esm2') is not a valid endpoint.

sequences: Input sequences layer: Optional layer number key: Response dict key containing embeddings. Auto-detected if None:

ESM2 models use “embeddings”, AbLang2 uses “seqcoding”, others default to “embedding”.

**kwargs: Additional arguments

Returns:

DataFrame with ‘sequence’, ‘sequence_id’, and ‘embedding’ columns

Example:
default
>>> df = Embed('esm2-8m', sequences=['MKTAYIAKQRQ', 'MKLAVID'])
class biolm.pipeline.EmbeddingSpec(key: str, layer: int | None = None, reduction: str | None = None)

Bases: object

Declarative specification for extracting embeddings from API responses.

Covers common response formats without writing a custom function.

Args:
key: Response dict key containing the embedding data (e.g.

"embedding", "seqcoding", "embeddings").

layer: Which layer to extract when the response contains multiple

layers (list of {layer: int, embedding: [...]} dicts). None stores all layers; an int stores only that layer.

reduction: Reduce per-token 2-D embeddings to a single vector:

"mean", "first", "last", "sum". None stores the full array as-is.

Examples:

default
# ablang2 returns {"seqcoding": [float, ...]}
EmbeddingSpec(key="seqcoding")

# esm2-8m returns {"embeddings": [{embedding: [...], layer: 33}]}
# Store only layer 33:
EmbeddingSpec(key="embeddings", layer=33)

# Per-residue → mean-pool:
EmbeddingSpec(key="embedding", reduction="mean")
key: str
layer: int | None = None
reduction: str | None = None
class biolm.pipeline.ExtractionSpec(response_key: str, reduction: str | None = None)

Bases: object

Specification for extracting a value from an API response (with reduction).

Use when you need to apply a reduction (mean, max, min, sum) to an array-valued response key. For simple scalar extractions, pass a plain string to extractions instead.

Args:

response_key: Key in API response dict, e.g. “plddt” reduction: Optional reduction for array values: “mean”, “max”, “min”, “sum”

reduction: str | None = None
response_key: str
class biolm.pipeline.FoldingEntity(id: str, entity_type: str, sequence: str | None = None, smiles: str | None = None, ccd: str | None = None)

Bases: object

A molecular entity for co-folding models (Boltz2, Chai-1).

Used with DataPipeline.add_cofolding_prediction() and GenerativePipeline.add_cofolding_prediction() to inject static entities — ligands, cofactors, DNA/RNA strands, or additional protein chains — alongside the pipeline’s primary sequences.

Args:

id: Chain identifier (Boltz uses single letter(s); Chai-1 uses a name). entity_type: Molecule type — 'protein', 'dna', 'rna', or

'ligand'.

sequence: Amino-acid / nucleotide sequence for protein / DNA / RNA entities. smiles: SMILES string for small-molecule ligands. ccd: CCD code for ligands defined in the Chemical Component Dictionary

(e.g. 'ATP', 'HEM').

ccd: str | None = None
entity_type: str
id: str
sequence: str | None = None
smiles: str | None = None
class biolm.pipeline.GenerativePipeline(generation_configs: list[Union[biolm.pipeline.generative.GenerationConfig, biolm.pipeline.mlm_remasking.RemaskingConfig, biolm.pipeline.generative.DirectGenerationConfig, biolm.pipeline.generative.SaturationMutagenesisConfig, biolm.pipeline.generative.IterativeMaskingDMSConfig]] | None = None, deduplicate: bool = True, configs: list[Union[biolm.pipeline.mlm_remasking.RemaskingConfig, biolm.pipeline.generative.DirectGenerationConfig, biolm.pipeline.generative.SaturationMutagenesisConfig, biolm.pipeline.generative.IterativeMaskingDMSConfig]] | None = None, filters=None, data_store=None, **kwargs)

Bases: BasePipeline

Pipeline for generating sequences and running predictions.

Supports: - Multiple generative models in parallel - Temperature scanning - Masked language model remasking - Downstream predictions and filtering

Args:

generation_configs: List of GenerationConfig objects deduplicate: Whether to deduplicate generated sequences datastore: DataStore instance or path run_id: Unique run ID output_dir: Output directory resume: Resume from previous run verbose: Enable verbose output

Example:
default
>>> # Generate with MPNN at multiple temperatures
>>> config1 = GenerationConfig(
...     model_name='proteinmpnn',
...     num_sequences=1000,
...     temperature=[0.5, 1.0, 1.5],
...     parent_sequence='MKTAYIAKQRQ'
... )
>>>
>>> # Also generate with ESM using remasking
>>> config2 = GenerationConfig(
...     model_name='esm2',
...     num_sequences=500,
...     generation_method='remask',
...     parent_sequence='MKTAYIAKQRQ',
...     mask_fraction=0.15
... )
>>>
>>> pipeline = GenerativePipeline(
...     generation_configs=[config1, config2]
... )
>>> pipeline.add_filter(ThresholdFilter('length', min_value=50))
>>> pipeline.add_prediction('esmfold', extractions='mean_plddt', columns='plddt')
>>> results = pipeline.run()
add_cofolding_prediction(model_name: str, action: str = 'predict', stage_name: str | None = None, prediction_type: str = 'structure', sequence_chain_id: str = 'A', sequence_entity_type: str = 'protein', static_entities: list[biolm.pipeline.generative.FoldingEntity] | None = None, depends_on: list[str] | None = None, params: dict | None = None, batch_size: int = 1)

Add a co-folding prediction stage (Boltz2, Chai-1).

Each sequence in the pipeline is treated as the primary protein chain. static_entities lets you inject additional molecules — ligands, cofactors, DNA/RNA strands, or extra protein chains — that are held constant across all designs.

Args:

model_name: BioLM model slug, e.g. 'boltz2' or 'chai1'. action: API action (default 'predict'). stage_name: Stage name (defaults to model_name). prediction_type: Column name for the confidence score (default

'structure').

sequence_chain_id: Chain ID assigned to the pipeline’s primary

sequence in the multi-molecule request (e.g. 'A').

sequence_entity_type: Molecule type for the primary sequence:

'protein', 'dna', or 'rna' (default 'protein').

static_entities: List of FoldingEntity objects — ligands,

cofactors, extra proteins/DNA/RNA — included in every request.

depends_on: Upstream stage names this stage waits for. params: Model-specific params dict (e.g.

{'recycling_steps': 3, 'sampling_steps': 20} for Boltz).

batch_size: Sequences per API request (default 1; co-folding models

are typically batch-size-1).

Example:

default
pipeline.add_cofolding_prediction(
    model_name='boltz2',
    static_entities=[
        FoldingEntity(id='L', entity_type='ligand', smiles='c1ccccc1'),
        FoldingEntity(id='B', entity_type='protein',
                      sequence='MKTAYIAKQRQ'),
    ],
    depends_on=['filter_top50'],
)
add_filter(filter_func, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)

Add a filter stage (same as DataPipeline).

add_generation_config(config: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig) GenerativePipeline

Append a config to the existing generation slot.

Use this to add a second model or temperature variant alongside the current generation config rather than replacing it. If there is no generation slot yet, one is created.

Args:

config: Config to add.

Returns:

Self, for method chaining.

add_prediction(model_name: str, action: str = 'predict', extractions=None, columns=None, params: dict | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)

Add a prediction stage (same as DataPipeline).

add_predictions(models: list[Union[str, dict]], action: str = 'predict', depends_on: list[str] | None = None, **kwargs)

Add multiple prediction stages at the same level (run in parallel).

Args:

models: List of model names or dicts with model configs action: Default action if not specified in dict depends_on: List of stage names all these depend on **kwargs: Default kwargs for all stages

Returns:

self for chaining

Example:
default
>>> pipeline.add_predictions(['temberture-regression', 'proteinmpnn', 'esm2'])
add_stage(stage: Stage) None

Add a stage to the pipeline.

If stage is a GenerationStage it replaces the current generation slot (there is always exactly one, at position 0) rather than appending. All other stage types are appended normally.

replace_generation(config: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig, stage_name: str | None = None, deduplicate: bool = True) GenerativePipeline

Swap the generation slot with a single new config.

Equivalent to set_generation(config, stage_name=stage_name). Kept for backwards compatibility and single-config convenience.

async run_async(**kwargs) dict[str, biolm.pipeline.base.StageResult]

Run the generative pipeline.

All GenerationStages (regardless of position) are extracted and run first as sources — their outputs are unioned into the initial WorkingSet so generated sequences trickle through every downstream prediction/filter stage (the “funnel”). Idempotent: self.stages is always restored after execution.

set_generation(*configs: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig, stage_name: str = 'generation', deduplicate: bool = True) GenerativePipeline

Set (or replace) the generation slot with one or more configs.

Multiple configs run in parallel — use this for multi-model generation or temperature scanning. Every call to .run() re-runs generation from scratch; downstream stages use their prediction cache so only truly new sequences are computed.

Args:
*configs: One or more RemaskingConfig or

DirectGenerationConfig objects.

stage_name: Name for the generation stage (default "generation"). deduplicate: Deduplicate across all configs (default True).

Returns:

Self, for method chaining.

Example:

default
# Single model
pipeline.set_generation(
    DirectGenerationConfig("dsm-150m-base", sequence=parent, num_sequences=200)
).run()

# Two models in parallel — sequences from both trickle through the funnel
pipeline.set_generation(
    DirectGenerationConfig("dsm-150m-base", sequence=parent, num_sequences=100),
    RemaskingConfig("esm-150m", mask_fraction=0.15),
).run()
use_sequences(sequences=None, column: str = 'sequence', stage_name: str = 'data_source', from_db: bool = False) GenerativePipeline

Use existing sequences as the pipeline source instead of generating.

Replaces the generation slot with a SequenceSourceConfig. Downstream prediction and filter stages run on the provided sequences, using the normal DuckDB prediction cache for anything already computed.

Args:

sequences: One of:

  • list[str] — plain amino-acid strings

  • pd.DataFrame — must contain column (default "sequence")

  • str / Path — CSV or FASTA file

  • None — reload all sequences already in the DuckDB

column: Column name when sequences is a DataFrame or CSV. stage_name: Name for the source stage (default "data_source"). from_db: Pull all sequences already present in this pipeline’s DuckDB.

Returns:

Self, for method chaining.

Example:

default
# Inject a list
pipeline.use_sequences(["MKTAY", "MKLLIV"]).run()

# Use all sequences already in the DB (e.g. recover + rerun)
pipeline.use_sequences(from_db=True).run()

# Load from CSV and run through the existing filter/predict stages
pipeline.use_sequences("candidates.csv").run()
class biolm.pipeline.GenerativeProtocolConfig

Bases: object

Marker base class for stages that produce new amino-acid sequences.

Subclasses drive generative or masked-language models to emit novel sequences — either via autoregressive sampling (ProteinMPNN, DSM, AntiFold) or by greedy-argmax masking over an MLM (ESM2, ESMC). Unlike ScoringProtocolConfig subclasses, these stages expand the candidate pool rather than filtering it; they are the source stage in a generative design pipeline.

Use isinstance(config, GenerativeProtocolConfig)() to branch on config type in pipeline dispatch functions.

Example:

default
from biolm.pipeline.generative import (
    GenerativeProtocolConfig,
    DirectGenerationConfig,
    IterativeMaskingDMSConfig,
)

def dispatch(config):
    if isinstance(config, GenerativeProtocolConfig):
        return run_generative_stage(config)  # emits new sequences
    return run_scoring_stage(config)          # returns ranked variants

cfg_direct = DirectGenerationConfig(
    "dsm-150m-base", sequence="MKTAYIAKQRQ", num_sequences=50
)
cfg_dms = IterativeMaskingDMSConfig(
    parent_sequence="MKTAYIAKQRQ", model_name="esm2-650m"
)
assert isinstance(cfg_direct, GenerativeProtocolConfig)  # True
assert isinstance(cfg_dms, GenerativeProtocolConfig)     # True
class biolm.pipeline.HammingDistanceFilter(reference_sequence: str, max_distance: float | None = None, min_distance: float | None = None, normalize: bool = False)

Bases: BaseFilter

Filter by Hamming distance to a reference sequence.

Args:

reference_sequence: Reference sequence max_distance: Maximum Hamming distance (inclusive) min_distance: Minimum Hamming distance (inclusive) normalize: If True, use normalized distance (0-1)

Example:
default
>>> filter = HammingDistanceFilter('MKTAYIAKQ', max_distance=5)
>>> df_filtered = filter(df)
static hamming_distance(seq1: str, seq2: str, normalize: bool = False) float

Calculate Hamming distance between two sequences.

For equal-length sequences this is the standard Hamming distance (count of positions where characters differ).

For sequences of different lengths (F09 — edge-case note): the distance is computed as the number of mismatches in the overlapping prefix PLUS the absolute difference in lengths (each extra character in the longer sequence counts as one mismatch). When normalize=True the result is divided by max(len(seq1), len(seq2)). This is a non-standard extension; callers comparing variable-length sequences should be aware that the normalized value is NOT equivalent to edit distance / Levenshtein normalized distance.

to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

class biolm.pipeline.InputSchema(columns: list[str])

Bases: object

Describes the input columns for a pipeline.

When set, these columns are the primary data — sequence is not required. Columns are stored directly on the sequences table via ALTER TABLE ADD COLUMN so that materialize_working_set(), SQL filters, and item_columns all work via direct JOINs.

Hashing uses all columns (sorted alphabetically) joined with \x00 separators so that identical rows produce the same hash regardless of column order.

Args:

columns: List of column names that comprise the primary input.

columns: list[str]
hash_row(row: dict[str, str]) str

SHA-256 hash of the row values across all input columns (sorted).

class biolm.pipeline.IterativeMaskingDMSConfig(parent_sequence: str, model_name: str, positions: list[int] | None = None, rounds: int = 2, mask_token: str = '<mask>', alphabet: str = 'ACDEFGHIKLMNPQRSTVWY', exclude_synonymous: bool = True, batch_size: int = 32, label: str | None = None, action: str = 'predict')

Bases: GenerativeProtocolConfig

Source config that builds multi-point variants via sequential greedy masking.

Implements an iterative argmax masking procedure using a masked language model:

  1. For each target position, mask it in the parent sequence and query the model for the highest-probability residue (greedy argmax — not sampled).

  2. If rounds > 1, apply the round-1 preferred substitution at each position and repeat for round 2: mask each other target position in the round-1 sequence and collect the argmax residue.

  3. Yield all resulting sequences as pipeline outputs.

This matches the ESM2 two-round DMS design pattern in the EGF generation notebook.

Args:

parent_sequence: Starting sequence. model_name: MLM model slug (e.g. 'esm2-650m', 'esmc-300m'). positions: 0-indexed positions to probe. Defaults to all positions. rounds: Number of sequential masking rounds (default 2). Round N uses

the variant produced by round N-1 as its starting sequence.

mask_token: Token inserted at masked positions (default ''). alphabet: Vocabulary used to identify valid AA positions in logits.

Defaults to the 20 canonical amino acids.

exclude_synonymous: Skip round-1 variants where the argmax matches WT

(default True).

batch_size: Sequences per API request (default 32). label: Optional label stored as source_label in results. action: API action for the model (default 'predict'; the model must

return logits). Only 'predict' is allowed because the greedy-argmax procedure requires per-token logit arrays.

Example:

default
from biolm.pipeline.generative import IterativeMaskingDMSConfig, GenerativePipeline

# Two-round greedy DMS at three positions using ESM2-650M
config = IterativeMaskingDMSConfig(
    parent_sequence="MKTAYIAKQRQ",
    model_name="esm2-650m",
    positions=[2, 5, 8],   # 0-indexed; None → all positions
    rounds=2,              # round 1: per-position; round 2: 2-point combos
    exclude_synonymous=True,
)

pipeline = GenerativePipeline(configs=[config])
results = pipeline.run()
# results DataFrame has columns:
#   sequence, dms_round, dms_pos1, dms_aa1, dms_pos2, dms_aa2
action: str = 'predict'
alphabet: str = 'ACDEFGHIKLMNPQRSTVWY'
batch_size: int = 32
exclude_synonymous: bool = True
label: str | None = None
mask_token: str = '<mask>'
model_name: str
parent_sequence: str
positions: list[int] | None = None
rounds: int = 2
to_spec() dict
class biolm.pipeline.MLMRemasker(config: RemaskingConfig, api_client=None, model_name: str | None = None)

Bases: object

Masked Language Model remasking utility.

Handles iterative masking and prediction for generating sequence variants using masked language models.

Args:

config: RemaskingConfig instance api_client: BioLM API client (optional, for actual predictions) model_name: Model name (e.g., ‘esm2’, ‘esm1v’)

Example:
default
>>> config = RemaskingConfig(mask_fraction=0.15, num_iterations=5)
>>> remasker = MLMRemasker(config, model_name='esm2')
>>> variants = remasker.generate_variants('MKTAYIAKQRQ', num_variants=10)
create_masked_sequence(sequence: str, positions: list[int]) str

Create masked sequence with mask token at specified positions.

Args:

sequence: Original sequence positions: Positions to mask

Returns:

Masked sequence

async generate_variant(parent_sequence: str, iteration: int = 0) tuple[str, dict[str, Any]]

Generate a single variant through remasking (async).

Args:

parent_sequence: Starting sequence iteration: Iteration number (for seeding)

Returns:

Tuple of (variant_sequence, metadata_dict)

async generate_variants(parent_sequence: str, num_variants: int = 100, deduplicate: bool = True) list[tuple[str, dict[str, Any]]]

Generate multiple variants through remasking (async, concurrent).

Uses asyncio.gather with a semaphore to generate variants concurrently rather than sequentially, significantly reducing wall-clock time when the API client supports concurrent calls.

Args:

parent_sequence: Starting sequence num_variants: Number of variants to generate deduplicate: Remove duplicate sequences (parent excluded when True)

Returns:

List of (variant_sequence, metadata) tuples

async iterative_refinement(sequence: str, fitness_function: callable, num_iterations: int = 10, population_size: int = 20, keep_top_k: int = 5) list[tuple[str, float, dict[str, Any]]]

Perform iterative refinement using remasking and a fitness function (async).

Args:

sequence: Starting sequence fitness_function: Function that scores sequences (higher is better) num_iterations: Number of refinement iterations population_size: Number of variants per iteration keep_top_k: Number of top sequences to keep per iteration

Returns:

List of (sequence, fitness, metadata) tuples for final population

async predict_masked_positions(sequence: str, mask_positions: list[int]) tuple[str, dict[int, float]]

Predict amino acids at masked positions.

Builds the masked sequence client-side (inserting config.mask_token at each position), sends it to the model’s predict endpoint, and decodes the returned logits with temperature/top-k/top-p sampling.

Falls back to reading a "sequence" key from the response if the model returns a filled sequence directly instead of logits.

Args:

sequence: Original (unmasked) sequence. mask_positions: 0-indexed positions to replace.

Returns:

Tuple of (predicted_sequence, confidences_dict)

select_mask_positions(sequence: str, confidences: ndarray | None = None) list[int]

Select positions to mask based on strategy.

Args:

sequence: Input sequence confidences: Optional confidence scores per position (for low_confidence strategy)

Returns:

List of positions to mask (0-indexed)

class biolm.pipeline.MatrixExtractionSpec(prefix: str = 'ddg', values_key: str = 'ddG_matrix.values', row_labels_key: str = 'ddG_matrix.residue_axis', col_labels_key: str = 'ddG_matrix.amino_acid_axis', mutation_key: str | None = None, value_key: str | None = None)

Bases: object

Flattens a per-mutation response into individual prediction rows.

Each mutation becomes a separate prediction row with prediction_type formatted as ‘{prefix}_{label}’ (e.g., ‘ddg_M1A’).

Two modes:
  • Matrix mode (SPURS): 2D array + row/col labels.

  • List mode (ThermoMPNN): list of dicts with mutation name + value.

Args:

prefix: Prediction type prefix (e.g. “ddg”). values_key: Dot-path to 2D array in response (matrix mode). row_labels_key: Dot-path to row labels (position labels). col_labels_key: Dot-path to column labels (amino acid labels). mutation_key: Dict key for mutation name (list mode). If set, uses list mode. value_key: Dict key for the numeric value (list mode).

col_labels_key: str = 'ddG_matrix.amino_acid_axis'
mutation_key: str | None = None
prefix: str = 'ddg'
row_labels_key: str = 'ddG_matrix.residue_axis'
value_key: str | None = None
values_key: str = 'ddG_matrix.values'
exception biolm.pipeline.PipelineAPIAuthError(status_code: int, payload: Any, model_name: str)

Bases: RuntimeError

Raised when the BioLM API returns 401/402 (auth/billing) during a stage.

These errors are not retriable per-item — every batch will hit the same failure — so the pipeline fails fast instead of producing a silent empty result. skip_on_error=True does NOT swallow this; callers always see an unambiguous failure with the upstream error payload.

class biolm.pipeline.PipelineContext(datastore: DuckDBDataStore, run_id: str)

Bases: object

Shared key-value store backed by DuckDB for inter-stage communication.

Stages can read/write arbitrary data through the context. Common use case: stage 1 predicts structures (stored in the structures table), stage 2 reads them for structure-conditioned generation.

Args:

datastore: The pipeline’s DuckDB datastore. run_id: Current pipeline run ID.

get(key: str, default: Any = None) Any

Retrieve a value from the pipeline context table.

get_structure(sequence_id: int, model_name: str | None = None) dict | None

Convenience: fetch a structure from the datastore’s structures table.

get_structures_for_ws(ws: WorkingSet, model_name: str | None = None) DataFrame

Fetch structures for all sequences in a WorkingSet.

set(key: str, value: Any)

Store a value in the pipeline context table.

class biolm.pipeline.PipelineMetadata(pipeline_id: str, cache_dir: Path, db_path: Path, run_id: str)

Bases: object

Metadata for a pipeline run — lets users retrieve and reuse cached results.

Attributes:

pipeline_id: Unique identifier for the pipeline’s cache directory. cache_dir: Path to the .biolm/pipelines/ cache directory. db_path: Path to the DuckDB database file inside the cache directory. run_id: The run ID for this execution (there can be multiple runs

sharing the same cache).

Example:

default
pipeline = DataPipeline(sequences=[...])
pipeline.run()
meta = pipeline.metadata
print(meta.pipeline_id)   # "20260302_143022_a1b2c3d4"
print(meta.cache_dir)     # ".biolm/pipelines/20260302_143022_a1b2c3d4"

# Later — reuse the same cache:
pipeline2 = DataPipeline(
    sequences=new_seqs,
    datastore=meta.db_path,   # or str(meta.cache_dir)
    resume=True,
)
cache_dir: Path
db_path: Path
pipeline_id: str
run_id: str
class biolm.pipeline.PipelinePlotter(pipeline, df: DataFrame | None = None)

Bases: object

Convenience class for plotting pipeline results.

Args:

pipeline: Pipeline instance df: Optional DataFrame (uses pipeline.get_final_data() if not provided)

Example:
default
>>> plotter = PipelinePlotter(pipeline)
>>> plotter.plot_funnel()
>>> plotter.plot_distribution('tm')
plot_correlation_matrix(**kwargs)

Plot correlation matrix.

plot_distribution(column: str, **kwargs)

Plot distribution of a column.

plot_distributions(columns: list[str] | None = None, **kwargs)

Alias for plot_predictions — plot distributions of prediction columns.

plot_diversity(reference_sequence: str | None = None, **kwargs)

Plot sequence diversity.

plot_funnel(**kwargs)

Plot pipeline funnel.

plot_predictions(columns: list[str] | None = None, **kwargs)

Plot distributions of all numeric prediction columns in a multi-panel layout.

Args:
columns: Specific columns to plot. If None, auto-detects all numeric

columns except sequence_id, length, and hash.

**kwargs: Forwarded to matplotlib (e.g. figsize, bins).

plot_scatter(x_col: str, y_col: str, **kwargs)

Plot scatter plot.

plot_temperature_scan(metric_col: str, **kwargs)

Plot temperature scan results.

biolm.pipeline.Predict(model_name: str, sequences: list[str] | DataFrame | str | Path, extractions: str | list | None = None, params: dict | None = None, **kwargs) DataFrame

Convenience function for single-step prediction.

Args:

model_name: BioLM model name sequences: Input sequences extractions: API response key(s) to extract. Required — pass the response

key for your model (e.g. extractions='prediction' for temberture, extractions='mean_plddt' for esmfold). Use a list or ExtractionSpec for multiple extractions.

params: Optional API parameters **kwargs: Additional arguments

Returns:

DataFrame with predictions

Example:
default
>>> df = Predict('temberture-regression', sequences=['MKTAYIAKQRQ'], extractions='prediction')
class biolm.pipeline.RankingFilter(column: str, n: int | None = None, ascending: bool = False, method: str = 'top', percentile: float | None = None)

Bases: BaseFilter

Filter by ranking - select top N or bottom N by a column value.

This filter REQUIRES complete data to rank all sequences.

Args:

column: Column name to rank by n: Number of sequences to select ascending: If True, select lowest values; if False, select highest (default) method: Ranking method (‘top’ for top N, ‘bottom’ for bottom N, ‘percentile’ for top/bottom %) percentile: If method=’percentile’, the percentile threshold (0-100)

Example:
default
>>> # Top 100 by Tm
>>> filter = RankingFilter('tm', n=100, ascending=False)
>>> df_filtered = filter(df)
>>>
>>> # Bottom 50 by hamming distance
>>> filter = RankingFilter('hamming_distance', n=50, ascending=True)
>>> df_filtered = filter(df)
>>>
>>> # Top 10% by pLDDT
>>> filter = RankingFilter('plddt', method='percentile', percentile=90)
>>> df_filtered = filter(df)
requires_complete_data: bool = True
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) str | None

Return a complete SQL SELECT that yields surviving sequence_id values.

The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single sequence_id column). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.

Args:

ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.

Example return value:

default
SELECT w.sequence_id
FROM _filter_ws w
INNER JOIN predictions p ON w.sequence_id = p.sequence_id
WHERE p.prediction_type = 'tm' AND p.value >= 60.0

Returns None (default) when the filter cannot be expressed as SQL. Filters that return None will be executed via DataFrame materialization.

class biolm.pipeline.RemaskingConfig(model_name: str = 'esm-150m', action: str = 'predict', mask_fraction: float = 0.15, mask_positions: str | list[int] = 'auto', num_iterations: int = 1, temperature: float = 1.0, top_k: int | None = None, top_p: float | None = None, mask_token: str = '<mask>', conserved_positions: list[int] | None = None, mask_strategy: str = 'random', block_size: int = 3, confidence_threshold: float = 0.8, parent_sequence: str | None = None, num_variants: int = 100)

Bases: object

Configuration for MLM remasking.

Args:

model_name: MLM model to use (e.g., ‘esm-150m’, ‘esm-650m’, ‘esm-3b’, ‘esm3’, ‘esmc’) mask_fraction: Fraction of positions to mask (default: 0.15) mask_positions: Specific positions to mask, or ‘auto’ for random num_iterations: Number of remasking iterations (default: 1) temperature: Sampling temperature (default: 1.0) top_k: Top-k sampling (default: None) top_p: Nucleus sampling (default: None) mask_token: Token to use for masking (default: ‘<mask>’) conserved_positions: Positions that should never be masked mask_strategy: Strategy for selecting positions (‘random’, ‘low_confidence’, ‘blocks’) block_size: Size of blocks for block masking (default: 3) confidence_threshold: Threshold for low-confidence masking (default: 0.8)

Example:
default
>>> # ESM2 150M remasking
>>> config = RemaskingConfig(
...     model_name='esm-150m',
...     mask_fraction=0.15,
...     num_iterations=5,
...     temperature=1.0
... )
>>>
>>> # ESM3 with higher temperature
>>> config = RemaskingConfig(
...     model_name='esm3',
...     mask_fraction=0.20,
...     temperature=1.5
... )
action: str = 'predict'
block_size: int = 3
confidence_threshold: float = 0.8
conserved_positions: list[int] | None = None
mask_fraction: float = 0.15
mask_positions: str | list[int] = 'auto'
mask_strategy: str = 'random'
mask_token: str = '<mask>'
model_name: str = 'esm-150m'
num_iterations: int = 1
num_variants: int = 100
parent_sequence: str | None = None
temperature: float = 1.0
to_spec() dict

Return a serializable dict for pipeline definition persistence.

top_k: int | None = None
top_p: float | None = None
class biolm.pipeline.SaturationMutagenesisConfig(parent_sequence: str, scoring_model: str, positions: list[int] | None = None, alphabet: str = 'ACDEFGHIKLMNPQRSTVWY', scoring_action: str = 'predict', scoring_params: dict[str, typing.Any] = <factory>, score_field: str = 'ddg', top_n: int | None = 50, ascending: bool = True, exclude_synonymous: bool = True, batch_size: int = 8, label: str | None = None, pdb_str: str | None = None, chain: str = 'A')

Bases: ScoringProtocolConfig

Source config that generates a single-mutant library and filters by a prediction model.

Enumerates every single amino-acid substitution at the specified positions, scores each variant with scoring_model using scoring_action, then returns the top-top_n variants ranked by score_field.

Typical use: ThermoMPNN-D or ESM2StabP-guided design — predict ΔΔG for all single-point mutants, keep the most stabilising ones.

Args:

parent_sequence: Wild-type sequence to mutate. scoring_model: BioLM model slug used to score variants (e.g. 'thermompnn-d'). positions: 0-indexed positions to enumerate. If None, all positions

in the sequence are enumerated.

alphabet: Amino acids to substitute. Defaults to the 20 canonical AAs. scoring_action: API action for the scoring model (default 'predict'). scoring_params: Extra params forwarded to the scoring model API. score_field: Key inside each model response that holds the numeric score

(default 'ddg'). Supports nested access with '.' separator, e.g. 'result.ddg'.

top_n: Number of top-scoring variants to retain (default 50). None

keeps all variants that receive a valid score.

ascending: If True, lower scores are better (e.g. negative ΔΔG means

stabilising). Defaults to True.

exclude_synonymous: If True (default), skip substitutions that are

identical to the wild-type residue.

batch_size: Sequences per API request when scoring (default 8). label: Optional label stored as source_label in results. pdb_str: Raw PDB file contents as a string (not a file path). When

provided, each scoring item is built as {"pdb": pdb_str, "mutations": [""], "chain": chain} instead of {"sequence": mutant_sequence}. Required by structure-aware models such as ThermoMPNN-D. Pass None (default) for sequence-only models like ESM2StabP.

chain: Chain identifier forwarded in structure-aware scoring items

(default 'A').

Example:

default
from biolm.pipeline.generative import SaturationMutagenesisConfig, GenerativePipeline

# Score all single-mutant variants at three positions with ThermoMPNN-D
config = SaturationMutagenesisConfig(
    parent_sequence="MKTAYIAKQRQ",
    scoring_model="thermompnn-d",
    positions=[3, 7, 10],          # 0-indexed; None → all positions
    score_field="ddg",             # key in API response holding ΔΔG
    top_n=25,                      # keep top-25 most stabilising variants
    ascending=True,                # lower ΔΔG = more stabilising
    pdb_str=open("protein.pdb").read(),  # required by structure-aware models
)

pipeline = GenerativePipeline(configs=[config])
pipeline.add_prediction("esmfold", extractions="mean_plddt", columns="plddt")
results = pipeline.run()
alphabet: str = 'ACDEFGHIKLMNPQRSTVWY'
ascending: bool = True
batch_size: int = 8
chain: str = 'A'
exclude_synonymous: bool = True
label: str | None = None
parent_sequence: str
pdb_str: str | None = None
positions: list[int] | None = None
score_field: str = 'ddg'
scoring_action: str = 'predict'
scoring_model: str
scoring_params: dict[str, Any]
to_spec() dict
top_n: int | None = 50
class biolm.pipeline.ScoringProtocolConfig

Bases: object

Marker base class for stages that score sequences and return a ranked subset.

Subclasses call a BioLM prediction model to assign a numeric score to each variant sequence enumerated by the config (e.g. all single-point mutants), then sort and return the top-N results. They do not produce wholly new sequences — the output is a scored, ranked subset of the candidate library built internally by the config (no separate generation step is needed).

The scoring_action field on concrete subclasses selects the client method: 'predict' calls BioLMApiClient.predict() and 'score' calls BioLMApiClient.score(). Both are allowlisted ('predict', 'score') to prevent arbitrary method dispatch.

Use isinstance(config, ScoringProtocolConfig)() to branch on config type in pipeline dispatch functions.

Example:

default
from biolm.pipeline.generative import (
    ScoringProtocolConfig,
    SaturationMutagenesisConfig,
)

def dispatch(config):
    if isinstance(config, ScoringProtocolConfig):
        return run_scoring_stage(config)  # returns ranked variant library
    return run_generative_stage(config)   # returns novel sequences

config = SaturationMutagenesisConfig(
    parent_sequence="MKTAYIAKQRQ",
    scoring_model="thermompnn-d",
    score_field="ddg",
)
assert isinstance(config, ScoringProtocolConfig)  # True
class biolm.pipeline.SequenceClusterer(method: Literal['kmeans', 'dbscan', 'hierarchical'] = 'kmeans', n_clusters: int | None = None, similarity_metric: Literal['embedding'] = 'embedding', eps: float = 0.5, min_samples: int = 5, random_state: int = 42, mini_batch: bool = False, max_sample: int | None = None)

Bases: object

Cluster sequences by similarity.

Supports multiple clustering algorithms and similarity metrics.

Performance Notes:
  • For >10k sequences with Hamming distance, consider using max_sample

  • Embedding-based clustering scales much better (O(n) with MiniBatch K-means)

  • Use mini_batch=True for very large datasets (>50k sequences)

Args:

method: Clustering algorithm (‘kmeans’, ‘dbscan’, ‘hierarchical’) n_clusters: Number of clusters (for kmeans/hierarchical) similarity_metric: How to measure sequence similarity eps: DBSCAN epsilon parameter min_samples: DBSCAN minimum samples per cluster mini_batch: Use MiniBatchKMeans for large datasets (faster, approximate) max_sample: Maximum sequences to use for distance matrix (None = all)

Example:
default
>>> # For large datasets, use sampling or mini-batch
>>> clusterer = SequenceClusterer(
...     method='kmeans',
...     n_clusters=100,
...     mini_batch=True  # Much faster for large N
... )
>>> result = clusterer.cluster(sequences)
cluster(sequences: list[str], embeddings: ndarray | None = None) ClusteringResult

Cluster sequences and return assignments.

Args:

sequences: List of protein sequences embeddings: Pre-computed embeddings (if using embedding metric)

Returns:

ClusteringResult with cluster assignments and metrics

class biolm.pipeline.SequenceLengthFilter(min_length: int | None = None, max_length: int | None = None)

Bases: BaseFilter

Filter by sequence length.

Args:

min_length: Minimum sequence length (inclusive) max_length: Maximum sequence length (inclusive)

Example:
default
>>> filter = SequenceLengthFilter(min_length=50, max_length=500)
>>> df_filtered = filter(df)
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) str | None

Return a complete SQL SELECT that yields surviving sequence_id values.

The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single sequence_id column). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.

Args:

ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.

Example return value:

default
SELECT w.sequence_id
FROM _filter_ws w
INNER JOIN predictions p ON w.sequence_id = p.sequence_id
WHERE p.prediction_type = 'tm' AND p.value >= 60.0

Returns None (default) when the filter cannot be expressed as SQL. Filters that return None will be executed via DataFrame materialization.

class biolm.pipeline.SequenceSourceConfig(sequences: list | DataFrame | str | Path | None = None, column: str = 'sequence', from_db: bool = False)

Bases: object

Use existing sequences as the generation-slot source — no API calls made.

Plug into set_generation() (or use pipeline.use_sequences()) to feed existing data through prediction/filter stages without generating new sequences. The provided sequences are added to the DuckDB via the normal dedup path, so sequences already present just return their existing IDs.

Args:

sequences: Source of sequences — one of:

  • list[str]: plain amino-acid strings

  • pd.DataFrame: must contain column (default "sequence")

  • str / Path: path to a CSV or FASTA (.fasta/.fa) file

  • None: reload all sequences already in the DuckDB (requires from_db=True OR leaving sequences as None)

column: Column name when sequences is a DataFrame or CSV

(default "sequence").

from_db: Pull all sequences already present in the pipeline’s DuckDB

instead of loading new ones. Equivalent to sequences=None.

Example:

default
# Inject a list
pipeline.use_sequences(["MKTAY", "MKLLIV"]).run()

# Use all sequences already in the DB (e.g. after from_db() recovery)
pipeline.use_sequences(from_db=True).run()

# Load from CSV
pipeline.use_sequences("candidates.csv").run()
column: str = 'sequence'
from_db: bool = False
sequences: list | DataFrame | str | Path | None = None
to_spec() dict

Serialize for pipeline definition persistence.

Live DataFrames are not serializable — on reconstruct we fall back to from_db=True so the existing DB sequences are reused. Plain list[str] sequences are included directly so from_db() reconstruction can replay the same input without requiring an existing DB (GEN-09 fix).

class biolm.pipeline.SingleStepPipeline(model_name: str, action: str = 'predict', sequences: list[str] | DataFrame | str | Path = None, params: dict | None = None, extractions=None, columns=None, embedding_extractor=None, **kwargs)

Bases: DataPipeline

Simplified pipeline for single-step predictions.

Convenience class for running a single prediction model on sequences.

Args:

model_name: BioLM model name action: API action (‘predict’, ‘encode’, ‘score’) sequences: Input sequences params: Optional API parameters **kwargs: Additional arguments passed to DataPipeline

Example:
default
>>> pipeline = SingleStepPipeline(
...     model_name='esmfold',
...     sequences=['MKTAYIAKQRQ', 'MKLAVID']
... )
>>> results = pipeline.run()
>>> df = pipeline.get_final_data()
class biolm.pipeline.Stage(name: str, cache_key: str | None = None, depends_on: list[str] | None = None, model_name: str | None = None, max_concurrent: int = 10)

Bases: ABC

Abstract base class for pipeline stages.

A stage represents a single processing step in the pipeline. It can filter data, compute predictions, or transform sequences.

Args:

name: Stage name (must be unique within pipeline) cache_key: Unused collision-dedup key (auto-derived by PredictionStage) depends_on: List of stage names this stage depends on model_name: Model name for predictions/structures max_concurrent: Maximum concurrent API calls (for rate limiting)

Class Attributes:
merge_mode: How this stage’s output is merged when it runs in parallel

with other stages. "intersect" (default) = only keep sequences that pass all parallel stages (correct for filters). "union" = keep sequences that appear in any parallel stage output (correct for independent prediction stages that may skip sequences on error but should not drop others).

merge_mode: str = 'intersect'
async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]

Legacy DataFrame interface — used by streaming mode and GenerationStage.

Subclasses may override this for backward compatibility or for cases where a DataFrame is the natural input (e.g. generation with an empty df).

abstract async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]

Process data using WorkingSet (DuckDB-native).

All stages must implement this method. Stages that need actual sequence data (e.g. ClusteringStage) should call datastore.materialize_working_set(ws) internally — that is the stage’s responsibility, not the pipeline’s.

Args:

ws: Input WorkingSet (set of sequence IDs). datastore: DataStore for reading/writing data. **kwargs: Additional arguments (e.g. run_id).

Returns:

Tuple of (output WorkingSet, StageResult).

to_spec() dict

Return a serializable dict describing this stage.

Used by BasePipeline.run_async() to persist pipeline definitions to DuckDB (enabling DataPipeline.from_db() recovery after kernel death).

Subclasses must override this. Raises NotImplementedError by default.

class biolm.pipeline.StageResult(stage_name: str, input_count: int, output_count: int, filtered_count: int = 0, cached_count: int = 0, computed_count: int = 0, elapsed_time: float = 0.0, metadata: dict[str, typing.Any] = <factory>)

Bases: object

Result from a pipeline stage.

cached_count: int = 0
computed_count: int = 0
elapsed_time: float = 0.0
filtered_count: int = 0
input_count: int
metadata: dict[str, Any]
output_count: int
stage_name: str
class biolm.pipeline.StructureSpec(key: str, format: str | None = None, plddt_key: str | None = None, index: int | None = 0)

Bases: object

Specification for extracting and storing a structure from an API response.

Args:

key: Response dict key containing the structure string (e.g. “pdb”, “cif”, “pdbs”). format: Structure format — “pdb” or “cif”. Auto-detected from key if None. plddt_key: Optional response key for a confidence score to store alongside. index: For list-valued keys (e.g. AF2 “pdbs”), which element to store (default 0).

detect_format() str
format: str | None = None
index: int | None = 0
key: str
plddt_key: str | None = None
class biolm.pipeline.ThresholdFilter(column: str, min_value: float | None = None, max_value: float | None = None, keep_na: bool = False)

Bases: BaseFilter

Filter by column value threshold.

Args:

column: Column name to filter on min_value: Minimum value (inclusive) max_value: Maximum value (inclusive) keep_na: Whether to keep rows with NaN values

Example:
default
>>> filter = ThresholdFilter('tm', min_value=60.0)
>>> df_filtered = filter(df)
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) str | None

Return a complete SQL SELECT that yields surviving sequence_id values.

The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single sequence_id column). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.

Args:

ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.

Example return value:

default
SELECT w.sequence_id
FROM _filter_ws w
INNER JOIN predictions p ON w.sequence_id = p.sequence_id
WHERE p.prediction_type = 'tm' AND p.value >= 60.0

Returns None (default) when the filter cannot be expressed as SQL. Filters that return None will be executed via DataFrame materialization.

class biolm.pipeline.ValidAminoAcidFilter(alphabet: str = 'ACDEFGHIKLMNPQRSTVWY', verbose: bool = True, column: str = 'sequence')

Bases: BaseFilter

Filter sequences to only those composed of valid amino acid characters.

Uses vectorized regex matching via str.match() (C-level regex engine), which is ~100x faster than .apply(lambda) at million-sequence scale.

Args:

alphabet: String of allowed characters (default: 20 standard amino acids) verbose: If True, print count of removed sequences

Example:
default
>>> filter = ValidAminoAcidFilter()
>>> df_filtered = filter(df)
to_spec() dict

Return a serializable dict describing this filter.

Subclasses should override this. The base implementation raises NotImplementedError so that pipeline definition serialization fails early for unsupported filter types.

to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) str | None

Return a complete SQL SELECT that yields surviving sequence_id values.

The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single sequence_id column). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.

Args:

ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.

Example return value:

default
SELECT w.sequence_id
FROM _filter_ws w
INNER JOIN predictions p ON w.sequence_id = p.sequence_id
WHERE p.prediction_type = 'tm' AND p.value >= 60.0

Returns None (default) when the filter cannot be expressed as SQL. Filters that return None will be executed via DataFrame materialization.

class biolm.pipeline.WorkingSet(sequence_ids: frozenset[int])

Bases: object

Lightweight set of sequence IDs — replaces DataFrame as inter-stage transport.

Stages operate on DuckDB directly and pass only the set of surviving sequence IDs to the next stage. Materialization to DataFrame happens once at get_final_data() time.

Memory: 1M IDs ≈ 28 MB (frozenset[int]) vs 500 MB+ DataFrame.

difference(other: WorkingSet) WorkingSet

Return IDs in self but not in other.

classmethod from_ids(ids) WorkingSet

Create from any iterable of ints.

intersect(other: WorkingSet) WorkingSet

Return a new WorkingSet containing only IDs present in both sets.

sequence_ids: frozenset[int]
to_list() list[int]

Return sorted list (useful for DuckDB queries).

union(other: WorkingSet) WorkingSet

Return a new WorkingSet containing IDs from either set.

biolm.pipeline.analyze_diversity(sequences: list[str], max_sample: int | None = 10000) dict

Convenience function for analyzing sequence diversity.

Args:

sequences: List of protein sequences max_sample: Reserved for future embedding-based pairwise stats

Returns:

Dictionary of diversity metrics (shannon_entropy, motif diversity, uniqueness)

Example:
default
>>> metrics = analyze_diversity(sequences)
>>> print(f"Entropy: {metrics['shannon_entropy']:.2f}")
biolm.pipeline.cif_to_pdb(cif_path: str, output_path: str | None = None) str

Convert a CIF structure file to PDB format.

Uses gemmi if available, falls back to biopython.

Args:

cif_path: Path to input CIF (.cif / .mmcif) file. output_path: Destination PDB path. Defaults to same name with .pdb extension.

Returns:

Path to written PDB file.

Raises:

ImportError: If neither gemmi nor biopython is installed.

biolm.pipeline.cluster_sequences(sequences: list[str], method: str = 'kmeans', n_clusters: int = 10, embeddings: ndarray | None = None, mini_batch: bool = False, max_sample: int | None = None, **kwargs) ClusteringResult

Convenience function for clustering sequences using embeddings.

Performance Tips:
  • For >50k sequences, use mini_batch=True

  • Requires pre-computed embeddings (similarity_metric=’embedding’)

Args:

sequences: List of protein sequences method: Clustering algorithm n_clusters: Number of clusters embeddings: Pre-computed embeddings (required) mini_batch: Use MiniBatchKMeans for faster (approximate) clustering max_sample: Reserved; not yet used **kwargs: Additional arguments for SequenceClusterer

Returns:

ClusteringResult

biolm.pipeline.combine_filters(*filters: BaseFilter) BaseFilter

Combine multiple filters into a single filter (applied sequentially).

Returns a CompositeFilter which is serializable (unlike the previous CustomFilter-based implementation) and supports SQL fast-path evaluation when all sub-filters implement to_sql().

Args:

*filters: Variable number of filter objects

Returns:

Combined filter

Example:
default
>>> filter = combine_filters(
...     ThresholdFilter('tm', min_value=60),
...     SequenceLengthFilter(min_length=100)
... )
biolm.pipeline.load_structure_string(path: str) tuple[str, str]

Load a structure file and return (format, content_string).

Args:

path: Path to a .pdb, .ent, .cif, or .mmcif file.

Returns:

Tuple of (format_str, content_str) where format_str is ‘pdb’ or ‘cif’.

Raises:

ValueError: If file extension is not recognised. FileNotFoundError: If file does not exist.

biolm.pipeline.pdb_to_cif(pdb_path: str, output_path: str | None = None) str

Convert a PDB structure file to CIF format.

Uses gemmi if available, falls back to biopython.

Args:

pdb_path: Path to input PDB (.pdb / .ent) file. output_path: Destination CIF path. Defaults to same name with .cif extension.

Returns:

Path to written CIF file.

Raises:

ImportError: If neither gemmi nor biopython is installed.

We speak the language of bio-AI

© 2022 - 2026 BioLM. All Rights Reserved.