Trickle In New Candidates to Your In Silico Design Pipeline

Add new sequences to a running campaign each round. Cached predictions serve immediately — no recomputation tax.




What you'll learn:

  • Persisting predictions across multiple pipeline runs with a named DuckDBDataStore
  • The anti-join cache lookup that skips already-predicted sequences
  • Changing filter thresholds with zero API calls
  • Querying the full campaign history

export BIOLMAI_TOKEN=your-token-here

Setup

import os
from biolmai.pipeline import (
    DataPipeline, DuckDBDataStore,
    ThresholdFilter, RankingFilter,
    ValidAminoAcidFilter, EmbeddingSpec,
    DiversitySamplingFilter,
)

TOKEN = os.environ.get("BIOLMAI_TOKEN", "")
if not TOKEN:
    raise EnvironmentError(
        "Set BIOLMAI_TOKEN before running.\n"
        "Get one at https://biolm.ai/ui/accounts/user-api-tokens/"
    )

Round 1: initial screen

500 sequences, two models. All predictions cached to campaign.duckdb.

BATCH_1 = [
    "GIGKFLHSAKKFGKAFVGEIMNS", "GLFDIIKKIAESF", "KLAKLAKKLAKLAK",
    "RRWWRRWWRR", "KWKLFKKI", "GIGKFLHSAK", "RLFDKIRQ",
    "GLFDIVKKVVGALGSL", "FLPLILRKIVTAL", "KWKWKWKWKW",
]  # In practice: 500 sequences

pipeline_v1 = DataPipeline(
    sequences=BATCH_1,
    datastore=DuckDBDataStore("campaign.duckdb"),
    run_id="screen_v1",
    verbose=True,
)
pipeline_v1.add_prediction("temberture-regression", extractions="prediction",
                            columns="melting_temperature")
pipeline_v1.add_prediction("biolmsol", extractions="solubility_score",
                            columns="solubility")
pipeline_v1.add_filter(ThresholdFilter("melting_temperature", min_value=40.0))
pipeline_v1.run()
print(f"Round 1: {len(BATCH_1)} sequences predicted and cached")
Added stage: PredictionStage('predict_melting_temperature')
Added stage: PredictionStage('predict_solubility', depends_on=['predict_melting_temperature'])
Added stage: FilterStage('filter_2', depends_on=['predict_solubility'])

############################################################
# Pipeline: DataPipeline
# Run ID: screen_v1
# Initial sequences: 10
# Streaming: ENABLED
############################################################

Execution plan: 3 level(s)
  Level 1: predict_melting_temperature
  Level 2: predict_solubility
  Level 3: filter_2

============================================================
Stage: predict_melting_temperature
Input: 10 sequences
  Cached: 0/10
  To compute: 10
  Calling temberture-regression.predict...
Completed: 10 sequences in 1 batches (max 5 concurrent)

StageResult(predict_melting_temperature: in=10, out=10, cached=0, computed=10, filtered=0, time=5.4s)
============================================================

[Stage: predict_solubility] (streaming to filter_2)
  Cached: 0/10
  To compute: 10 (streaming)
predict_solubility: processed 10 sequences
  filter_2: 10 passed filter (filtered 0)

############################################################
# Pipeline completed in 9.2s
# Final sequences: 10
############################################################

Round 1: 10 sequences predicted and cached

Round 2: new sequences arrive

Feed the full combined list — old + new — into the same DB. Only new sequences hit the API.

BATCH_2 = [
    "GIKKFLGSIWKFIKAFVKEIMN", "RRLCRIVVIRVCR", "RRWQWR",
    "RWRWRW", "FKRIVQRIKDFL", "KWKLFKKIPKFLHLAK",
]  # In practice: 200 new sequences

ALL_SEQUENCES = BATCH_1 + BATCH_2

pipeline_v2 = DataPipeline(
    sequences=ALL_SEQUENCES,
    datastore=DuckDBDataStore("campaign.duckdb"),  # same DB
    run_id="screen_v2",
    verbose=True,
)
pipeline_v2.add_prediction("temberture-regression", extractions="prediction",
                            columns="melting_temperature")
pipeline_v2.add_prediction("biolmsol", extractions="solubility_score",
                            columns="solubility")
pipeline_v2.add_filter(ThresholdFilter("melting_temperature", min_value=40.0))
pipeline_v2.run()
print(f"Round 2: only {len(BATCH_2)} new sequences hit the API — {len(BATCH_1)} served from cache")
Added stage: PredictionStage('predict_melting_temperature')
Added stage: PredictionStage('predict_solubility', depends_on=['predict_melting_temperature'])
Added stage: FilterStage('filter_2', depends_on=['predict_solubility'])

############################################################
# Pipeline: DataPipeline
# Run ID: screen_v2
# Initial sequences: 16
# Streaming: ENABLED
############################################################

Execution plan: 3 level(s)
  Level 1: predict_melting_temperature
  Level 2: predict_solubility
  Level 3: filter_2

============================================================
Stage: predict_melting_temperature
Input: 16 sequences
  Cached: 10/16
  To compute: 6
  Calling temberture-regression.predict...
Completed: 6 sequences in 1 batches (max 5 concurrent)

StageResult(predict_melting_temperature: in=16, out=16, cached=10, computed=6, filtered=0, time=3.6s)
============================================================

[Stage: predict_solubility] (streaming to filter_2)
  Cached: 10/16
  To compute: 6 (streaming)
predict_solubility: processed 16 sequences
  filter_2: 16 passed filter (filtered 0)

############################################################
# Pipeline completed in 6.2s
# Final sequences: 16
############################################################

Round 2: only 6 new sequences hit the API — 10 served from cache

Round 3: tighten filters, zero API calls

Wet-lab results suggest the Tm threshold should be higher. No new sequences — just a stricter filter.

pipeline_v3 = DataPipeline(
    sequences=ALL_SEQUENCES,
    datastore=DuckDBDataStore("campaign.duckdb"),
    run_id="screen_v3",
    verbose=True,
)
pipeline_v3.add_prediction("temberture-regression", extractions="prediction",
                            columns="melting_temperature")
pipeline_v3.add_prediction("biolmsol", extractions="solubility_score",
                            columns="solubility")
pipeline_v3.add_filter(ThresholdFilter("melting_temperature", min_value=55.0))  # stricter
pipeline_v3.run()
pipeline_v3.summary()
Added stage: PredictionStage('predict_melting_temperature')
Added stage: PredictionStage('predict_solubility', depends_on=['predict_melting_temperature'])
Added stage: FilterStage('filter_2', depends_on=['predict_solubility'])

############################################################
# Pipeline: DataPipeline
# Run ID: screen_v3
# Initial sequences: 16
# Streaming: ENABLED
############################################################

Execution plan: 3 level(s)
  Level 1: predict_melting_temperature
  Level 2: predict_solubility
  Level 3: filter_2

============================================================
Stage: predict_melting_temperature
Input: 16 sequences
  Cached: 16/16
  To compute: 0

StageResult(predict_melting_temperature: in=16, out=16, cached=16, computed=0, filtered=0, time=0.0s)
============================================================

[Stage: predict_solubility] (streaming to filter_2)
  Cached: 16/16
  To compute: 0 (streaming)
  predict_solubility: processed 16 sequences
  filter_2: 11 passed filter (filtered 5)

############################################################
# Pipeline completed in 0.1s
# Final sequences: 11
############################################################
Stage Input Output Filtered Cached Computed Time (s)
0 predict_melting_temperature 16 16 0 16 0 0.0
1 predict_solubility 16 16 0 0 0 0.0
2 filter_2 16 11 5 0 0 0.0

Query campaign history

The database contains every prediction across all rounds. Fully queryable.

ds = DuckDBDataStore("campaign.duckdb")
ds.conn.execute("""
    SELECT s.sequence,
           MAX(CASE WHEN p.prediction_type = 'melting_temperature' THEN ROUND(p.value,1) END) AS tm,
           MAX(CASE WHEN p.prediction_type = 'solubility' THEN ROUND(p.value,3) END) AS solubility
    FROM sequences s
    JOIN predictions p ON s.sequence_id = p.sequence_id
    GROUP BY s.sequence
    ORDER BY tm DESC
""").df()
sequence tm solubility
0 KLAKLAKKLAKLAK 79.1 2.327
1 GIKKFLGSIWKFIKAFVKEIMN 78.9 0.615
2 FKRIVQRIKDFL 74.7 1.318
3 KWKLFKKI 73.3 1.766
4 KWKLFKKIPKFLHLAK 67.6 1.308
5 GIGKFLHSAKKFGKAFVGEIMNS 62.5 0.458
6 GLFDIIKKIAESF 61.5 0.368
7 RRWWRRWWRR 60.3 1.276
8 KWKWKWKWKW 55.9 0.868
9 GLFDIVKKVVGALGSL 55.7 0.027
10 RLFDKIRQ 55.5 1.578
11 RWRWRW 54.0 0.668
12 RRWQWR 53.0 0.654
13 GIGKFLHSAK 51.6 0.209
14 FLPLILRKIVTAL 51.2 0.179
15 RRLCRIVVIRVCR 48.5 0.926

Cleanup

ds.close()
import os; os.remove("campaign.duckdb")

Next Steps

Check out additional tutorials at jupyter.biolm.ai, or head over to our BioLM Documentation to explore additional models and functionality.

See more use-cases and APIs on your BioLM Console Catalog.


BioLM hosts deep learning models and runs inference at scale. You do the science.

Contact us to learn more.

Accelerate yourLead generation

BioLM offers tailored AI solutions to meet your experimental needs. We deliver top-tier results with our model-agnostic approach, powered by our highly scalable and real-time GPU-backed APIs and years of experience in biological data modeling, all at a competitive price.

CTA

We speak the language of bio-AI

© 2022 - 2026 BioLM. All Rights Reserved.

Run a Screening Campaign Across Multiple Batches Without Repeating Predictions | BioLM