Guaranteed 15% off your current AI inference bill for team spending up to $20000 / month.

Book a call →
Back to Blogs
Learn AI

Using LLMs in Biology: A Guide

Biologists are drowning in papers. I built a small structured extraction agent that reads a raw experimental abstract and returns genes, proteins, cell types...

Using LLMs in Biology: A Guide

Biologists are drowning in papers. I built a small structured extraction agent that reads a raw experimental abstract and returns genes, proteins, cell types, and suggested follow-up experiments in a machine-readable format. In this guide I will walk through the exact code so you can adapt it to your own literature pipeline or lab notebook.

What you'll need

You will need Python 3.10 or newer and the OpenAI SDK. Install it with pip install openai. I also use Pydantic to validate outputs, so run pip install pydantic. Finally, grab an API key from the Oxlo.ai portal at https://portal.oxlo.ai. Oxlo.ai is a developer-first inference platform that uses flat per-request pricing instead of token-based billing. That matters for biology workflows because abstracts, methods sections, and figure captions can get long. You can review the plans at https://oxlo.ai/pricing.

Step 1: Design the extraction schema

Before I write any LLM code, I define the data model. Biology literature is messy, so locking the output into a schema prevents the model from drifting between free-text summaries and structured data. I use Pydantic because it gives me validation, type hints, and clean serialization for downstream analysis. The BiologicalEntity class captures the name, a closed-set category, and the verbatim context so I can trace every claim back to the original sentence. The Relationship class stores subject-predicate-object triples, which are useful for building knowledge graphs or Cypher queries later. I also ask for a follow_up list so the pipeline suggests concrete experiments, which is handy for lab meeting prep or grant gap analysis.

from pydantic import BaseModel, Field
from typing import List

class BiologicalEntity(BaseModel):
    name: str
    category: str = Field(description="one of: gene, protein, cell_type, chemical, disease")
    context: str = Field(description="verbatim sentence or phrase where it appears")

class Relationship(BaseModel):
    subject: str
    predicate: str
    object: str

class Extraction(BaseModel):
    entities: List[BiologicalEntity]
    relationships: List[Relationship]
    follow_up: List[str] = Field(description="suggested experiments or validations")

Step 2: Write the system prompt

The system prompt is the only behavioral contract I have with the model. I keep it strict. I embed the JSON schema directly in the prompt, enumerate the allowed entity categories, and explicitly forbid markdown code blocks. This reduces the chance that the model wraps its answer in triple backticks, which would break json.loads. I also instruct it to keep predicates as short verbs so downstream knowledge graphs stay clean and predictable. If you prefer, Oxlo.ai supports JSON mode, but a strong system prompt works across every model and keeps the script self-contained.

SYSTEM_PROMPT = """You are a biology literature extraction assistant. Your job is to read the user-provided text and return a single JSON object matching this schema:

{
  "entities": [
    {"name": "...", "category": "...", "context": "..."}
  ],
  "relationships": [
    {"subject": "...", "predicate": "...", "object": "..."}
  ],
  "follow_up": ["...", "..."]
}

Rules:
- Categories are limited to: gene, protein, cell_type, chemical, disease.
- Predicates must be short verbs such as activates, inhibits, expresses, phosphorylates.
- Follow-up suggestions should be concrete experimental next steps.
- Do not include markdown formatting, explanations, or text outside the JSON object."""

Step 3: Build the client and call Oxlo.ai

I initialize the OpenAI SDK against Oxlo.ai's base URL. In my testing, Llama 3.3 70B follows this JSON schema reliably for biology text, but Oxlo.ai also hosts Qwen 3 32B if you are working with multilingual literature, or Kimi K2.6 if you need advanced reasoning over complex pathways. Because Oxlo.ai offers fully OpenAI-compatible endpoints, the only difference from a standard OpenAI script is the base_url and the model name. I set the client once and wrap the call in a small function that returns a Python dict. In production you should load the API key from an environment variable, but I keep it inline here for clarity.

import json
from openai import OpenAI

client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")

def fetch_raw_extraction(abstract: str):
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": abstract},
        ],
    )
    return json.loads(response.choices[0].message.content)

Step 4: Validate and wrap the pipeline

Raw JSON from an LLM can hallucinate keys or return malformed lists. I harden the pipeline by validating every response against the Pydantic model. If the shape is wrong, the validation error propagates immediately so I do not pollute my database with bad records. I keep the extraction logic inside a single function that both calls Oxlo.ai and validates the result, making it easy to drop into a FastAPI route or a Jupyter notebook. I also catch json.JSONDecodeError so a rare malformed payload does not crash the whole loop.

import json
from pydantic import ValidationError
from openai import OpenAI

client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")

def extract(abstract: str) -> Extraction:
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": abstract},
        ],
    )
    try:
        data = json.loads(response.choices[0].message.content)
    except json.JSONDecodeError as exc:
        raise ValueError(f"Invalid JSON returned: {exc}") from exc

    try:
        return Extraction.model_validate(data)
    except ValidationError as exc:
        raise ValueError(f"Schema mismatch: {exc}") from exc

Step 5: Batch process multiple abstracts

In a real literature review you will not stop at one paper. This loop feeds a list of abstracts through the extraction function and collects validated results. Oxlo.ai has no cold starts on popular models, so the loop runs at a steady pace without warmup latency. If you need to scale further, you can parallelize the loop with concurrent.futures or move it to an async worker. The core request pattern stays identical no matter how you orchestrate it.

from typing import List

def process_papers(abstracts: List[str]) -> List[Extraction]:
    findings = []
    for text in abstracts:
        findings.append(extract(text))
    return findings

Run it

I test the pipeline on a short abstract covering the p53-MDM2 axis. I pass the raw text straight into the extract function. The model identifies the proteins, the small molecule, the cell line, and the downstream transcripts. It also suggests a co-immunoprecipitation experiment to confirm the physical interaction. Here is the call and the output.

ABSTRACT = """
The tumor suppressor p53 is negatively regulated by MDM2, an E3 ubiquitin ligase that promotes p53 degradation. 
Here we show that the small-molecule inhibitor nutlin-3a disrupts the p53-MDM2 interaction, leading to p53 accumulation and cell-cycle arrest in HCT116 colon cancer cells. 
RNA-seq further revealed upregulation of p21 and Bax transcripts.
"""

finding = extract(ABSTRACT)
print(finding.model_dump_json(indent=2))

The printed result looks like this:

{
  "entities": [
    {"name": "p53", "category": "protein", "context": "The tumor suppressor p53 is negatively regulated by MDM2"},
    {"name": "MDM2", "category": "protein", "context": "negatively regulated by MDM2, an E3 ubiquitin ligase"},
    {"name": "nutlin-3a", "category": "chemical", "context": "small-molecule inhibitor nutlin-3a disrupts the p53-MDM2 interaction"},
    {"name": "HCT116", "category": "cell_type", "context": "cell-cycle arrest in HCT116 colon cancer cells"},
    {"name": "p21", "category": "gene", "context": "upregulation of p21 and Bax transcripts"},
    {"name": "Bax", "category": "gene", "context": "upregulation of p21 and Bax transcripts"}
  ],
  "relationships": [
    {"subject": "MDM2", "predicate": "inhibits", "object": "p53"},
    {"subject": "nutlin-3a", "predicate": "disrupts", "object": "p53-MDM2 interaction"},
    {"subject": "p53", "predicate": "upregulates", "object": "p21"}
  ],
  "follow_up": [
    "Perform co-immunoprecipitation to confirm p53-MDM2 disruption by nutlin-3a in HCT116 cells.",
    "Run a dose-response assay to quantify IC50 of nutlin-3a for p53 accumulation.",
    "Validate p21 and Bax upregulation by quantitative PCR."
  ]
}

Next steps

Two concrete ways to extend this pipeline.

First, switch from abstracts to full article PDFs. Oxlo.ai uses flat per-request pricing, so you can paste an entire methods section or supplementary figure caption without increasing the API cost. That makes whole-paper analysis practical in a way that token-based billing discourages.

Second, build a semantic search layer over your extractions. Oxlo.ai hosts embedding models such as BGE-Large and E5-Large. You can vectorize each extracted entity or relationship, store the vectors in a local database like pgvector or Chroma, and then query across your literature findings with natural language. This turns a static reading list into a living knowledge base you can interrogate without exact keyword matching.

Ready to build with Oxlo.ai?

Get started building high-performance AI inference applications today.

Get started
Ox Assistant
Online
OxBot
OxBot

Hi there! Try our cost calculator to see what you'd save with Oxlo.ai.