We are going to build a biology research assistant that takes a gene symbol, retrieves structured context, and generates a concise research brief with a testable hypothesis. It is useful for biologists and bioinformaticians who need to triage literature on unfamiliar genes. We will run it on Oxlo.ai using a flat per-request pricing model that stays cheap even when we feed in long abstracts.
What you'll need
Python 3.10 or newer installed locally. The OpenAI SDK, which you can install with pip install openai. An Oxlo.ai API key from https://portal.oxlo.ai. Their request-based pricing means long gene summaries do not inflate the cost, which helps when you expand this to full paper abstracts.
Step 1: Configure the Oxlo.ai client
We start by instantiating the OpenAI-compatible client pointing at Oxlo.ai. I use llama-3.3-70b as the default workhorse because it handles structured instructions reliably.
from openai import OpenAI
import json
import os
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.environ.get("OXLO_API_KEY", "YOUR_OXLO_API_KEY")
)
# Quick connectivity test
test = client.chat.completions.create(
model="llama-3.3-70b",
messages=[{"role": "user", "content": "Say 'Oxlo.ai client ready'"}],
max_tokens=10
)
print(test.choices[0].message.content)
Step 2: Build a mock biological knowledge resolver
In production you might query UniProt or NCBI Entrez. Here we simulate a retrieval layer with a dictionary of gene records so the tutorial stays self-contained and runnable offline except for the LLM call.
GENE_DB = {
"TP53": {
"full_name": "Tumor protein p53",
"function": "Transcription factor that regulates cell cycle, apoptosis, and DNA repair.",
"diseases": ["Li-Fraumeni syndrome", "various carcinomas"],
"pathways": ["p53 signaling", "Cell cycle checkpoint"],
"summary": "Often called the guardian of the genome, TP53 is the most frequently mutated gene in human cancer."
},
"BRCA1": {
"full_name": "Breast cancer type 1 susceptibility protein",
"function": "Involved in DNA repair via homologous recombination, transcriptional regulation, and ubiquitination.",
"diseases": ["Hereditary breast and ovarian cancer syndrome"],
"pathways": ["Homologous recombination", "Fanconi anemia pathway"],
"summary": "BRCA1 mutations confer high lifetime risk of breast and ovarian cancer."
},
"APOE": {
"full_name": "Apolipoprotein E",
"function": "Lipid transport and metabolism, neuronal maintenance.",
"diseases": ["Alzheimer disease", "hyperlipoproteinemia type III"],
"pathways": ["Lipid metabolism", "Alzheimer disease pathway"],
"summary": "The epsilon4 allele of APOE is the strongest genetic risk factor for late-onset Alzheimer disease."
}
}
def retrieve_gene_context(symbol: str) -> str:
record = GENE_DB.get(symbol.upper())
if not record:
return f"No local record for {symbol}. The agent will rely on parametric knowledge only."
return json.dumps(record, indent=2)
Step 3: Define the system prompt
The system prompt tells the model how to format output and how to reason biologically. Keeping it in a dedicated variable makes it easy to iterate without touching business logic.
SYSTEM_PROMPT = """You are a biology research assistant. Your job is to synthesize the provided gene context into a structured research brief.
Rules:
1. If structured context is provided, prioritize it over your internal knowledge.
2. Output valid JSON with exactly these keys: gene_symbol, full_name, function, related_diseases, pathways, literature_summary, testable_hypothesis.
3. The testable_hypothesis must propose a concrete experiment relating the gene to one of the listed diseases or pathways.
4. Be concise. Use plain language but preserve biological accuracy.
Respond only with the JSON object. Do not wrap it in markdown code fences.
"""
Step 4: Build the agent core
This function assembles the user query, appends the retrieved context, and calls Oxlo.ai. I pass the context as part of the user message to keep the conversation state simple.
def analyze_gene(gene_symbol: str, model: str = "llama-3.3-70b") -> dict:
context = retrieve_gene_context(gene_symbol)
user_message = f"Gene: {gene_symbol.upper()}\n\nContext:\n{context}"
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
temperature=0.2,
max_tokens=1024
)
raw = response.choices[0].message.content.strip()
# Strip accidental markdown fences if the model produces them
if raw.startswith("
```json"):
raw = raw.split("```
json", 1)[1]
if raw.endswith("
```"):
raw = raw.rsplit("```
", 1)[0]
return json.loads(raw.strip())
Step 5: Add a deep reasoning variant
For hypothesis generation it helps to use a reasoning model. Oxlo.ai offers deepseek-v3.2, which is strong at coding and reasoning and also has a free tier. We will add a second function that swaps the model and asks for a more exploratory analysis.
def analyze_gene_deep(gene_symbol: str) -> dict:
context = retrieve_gene_context(gene_symbol)
user_message = (
f"Gene: {gene_symbol.upper()}\n\n"
f"Context:\n{context}\n\n"
"In addition to the JSON brief, include a short 'mechanistic_rationale' paragraph "
"explaining the molecular logic behind your hypothesis."
)
response = client.chat.completions.create(
model="deepseek-v3.2",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
temperature=0.3,
max_tokens=1200
)
raw = response.choices[0].message.content.strip()
if raw.startswith("
```json"):
raw = raw.split("```
json", 1)[1]
if raw.endswith("
```"):
raw = raw.rsplit("```
", 1)[0]
return json.loads(raw.strip())
Run it
Here is how I test the agent from the command line. The first call uses the default Llama model for speed, and the second uses DeepSeek V3.2 for deeper reasoning.
if __name__ == "__main__":
print("=== TP53 Brief (Llama 3.3 70B) ===")
brief = analyze_gene("TP53")
print(json.dumps(brief, indent=2))
print("\n=== BRCA1 Deep Analysis (DeepSeek V3.2) ===")
deep_brief = analyze_gene_deep("BRCA1")
print(json.dumps(deep_brief, indent=2))
Example output for the TP53 call:
{
"gene_symbol": "TP53",
"full_name": "Tumor protein p53",
"function": "Transcription factor that regulates cell cycle, apoptosis, and DNA repair.",
"related_diseases": ["Li-Fraumeni syndrome", "various carcinomas"],
"pathways": ["p53 signaling", "Cell cycle checkpoint"],
"literature_summary": "Often called the guardian of the genome, TP53 is the most frequently mutated gene in human cancer.",
"testable_hypothesis": "Knockdown of mutant TP53 in Li-Fraumeni syndrome derived fibroblasts will restore G1/S checkpoint integrity and reduce spontaneous DNA double-strand breaks compared to non-targeting controls."
}
Wrap-up
To make this production ready, replace the GENE_DB dictionary with live calls to the NCBI Entrez or UniProt APIs, and cache the retrieved text in a vector store for reuse. If you expand the context to full paper abstracts, Oxlo.ai request-based pricing keeps costs flat regardless of prompt length, which matters when you start feeding 10,000 token summaries into the model. You can view the latest plans at https://oxlo.ai/pricing.
Top comments (0)