We are going to build a biomedical entity extractor that adapts a general-purpose LLM to the clinical domain using in-context transfer learning. This is useful for engineers who need structured medication data from unstructured clinical notes without training a new model from scratch. By the end, you will have a working Python service that calls Oxlo.ai and returns validated JSON.
What you'll need
Before starting, grab an Oxlo.ai API key from https://portal.oxlo.ai. You will also need Python 3.10 or newer and the OpenAI SDK installed.
pip install openai
Step 1: Configure the Oxlo.ai client
Oxlo.ai exposes a fully OpenAI-compatible API, so we can use the standard SDK and point it at their endpoint. I use llama-3.3-70b here because it follows instructions reliably for structured extraction tasks, and Oxlo.ai's flat per-request pricing keeps costs predictable even when we prepend long few-shot prompts.
from openai import OpenAI
import json
import re
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
Step 2: Define the domain system prompt
The system prompt is where the transfer happens. We inject domain terminology, the output schema, and constraints to repurpose the general model for clinical entity extraction.
SYSTEM_PROMPT = """You are a clinical entity extraction model adapted for medication dosage identification.
Extract all medications, dosages, routes, and frequencies from the provided clinical note.
Respond only with a JSON object matching this schema:
{
"medications": [
{"name": "string", "dosage": "string", "route": "string", "frequency": "string"}
]
}
If a field is not mentioned, use null. Do not include explanatory text outside the JSON."""
Step 3: Build few-shot examples for in-context transfer
Few-shot examples are the core of in-context transfer learning. They teach the model the target distribution without gradient updates. I embed two examples directly in the message history.
FEW_SHOT_EXAMPLES = [
{
"role": "user",
"content": "Patient was started on metformin 500 mg PO BID for type 2 diabetes."
},
{
"role": "assistant",
"content": json.dumps({
"medications": [
{"name": "metformin", "dosage": "500 mg", "route": "PO", "frequency": "BID"}
]
})
},
{
"role": "user",
"content": "Discharge meds: lisinopril 10 mg PO daily, atorvastatin 20 mg PO qHS."
},
{
"role": "assistant",
"content": json.dumps({
"medications": [
{"name": "lisinopril", "dosage": "10 mg", "route": "PO", "frequency": "daily"},
{"name": "atorvastatin", "dosage": "20 mg", "route": "PO", "frequency": "qHS"}
]
})
}
]
Step 4: Create the extraction function
Now we wire the client, system prompt, and few-shot examples into a single function. I prepend the few-shot messages before each new user query so the model sees the pattern every time.
def extract_medications(clinical_note: str) -> dict:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
] + FEW_SHOT_EXAMPLES + [
{"role": "user", "content": clinical_note},
]
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=messages,
temperature=0.1,
max_tokens=512,
)
raw = response.choices[0].message.content
# Strip markdown fences if the model emits them
cleaned = re.sub(r"^
```json\s*|^```
\s*|
```
$", "", raw, flags=re.MULTILINE).strip()
return json.loads(cleaned)
Step 5: Add validation and error handling
Real pipelines need graceful failures. This wrapper catches JSON parsing errors and schema mismatches, then returns a safe fallback.
def safe_extract(clinical_note: str) -> dict:
try:
result = extract_medications(clinical_note)
if "medications" not in result:
return {"medications": [], "error": "Schema mismatch", "raw": str(result)}
return result
except Exception as e:
return {"medications": [], "error": str(e), "note": clinical_note}
Run it
Here is how we call the finished agent with a real clinical snippet. Because Oxlo.ai uses request-based pricing, adding those few-shot examples does not increase the per-request cost, which makes iterative prompt engineering affordable for long-context workloads. See https://oxlo.ai/pricing for details.
if __name__ == "__main__":
note = (
"The patient reports continued chest pain. "
"Prescribed aspirin 81 mg PO daily and nitroglycerin 0.4 mg SL q5min PRN. "
"Continue home regimen of metoprolol 50 mg PO BID."
)
result = safe_extract(note)
print(json.dumps(result, indent=2))
Example output:
{
"medications": [
{"name": "aspirin", "dosage": "81 mg", "route": "PO", "frequency": "daily"},
{"name": "nitroglycerin", "dosage": "0.4 mg", "route": "SL", "frequency": "q5min PRN"},
{"name": "metoprolol", "dosage": "50 mg", "route": "PO", "frequency": "BID"}
]
}
Next steps
To push this further, connect the extractor to a vector database of drug interaction guidelines and prepend retrieved context into the system prompt for retrieval-augmented domain adaptation. You could also swap llama-3.3-70b for qwen-3-32b on Oxlo.ai if you are processing multilingual clinical notes, since both models are available under the same flat per-request pricing with no cold starts.
Top comments (0)