We are going to build a lightweight entity recognition pipeline that turns raw text into structured JSON. This is useful for anyone processing news articles, support tickets, or legal documents who does not want to maintain a custom spaCy model. Because Oxlo.ai charges a flat rate per request instead of per token, feeding it a long deposition or a multi-page brief does not inflate the cost.
What you'll need
- Python 3.10 or newer
- The OpenAI SDK installed with
pip install openai - An Oxlo.ai API key from https://portal.oxlo.ai. I will use the
llama-3.3-70bmodel because it handles instruction following cleanly.
Step 1: Set up the Oxlo.ai client
I start by importing the OpenAI SDK and pointing it at Oxlo.ai. The base URL and key are the only changes needed to make this a drop-in replacement.
from openai import OpenAI
import os
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.environ.get("OXLO_API_KEY", "YOUR_OXLO_API_KEY"),
)
Step 2: Define the schema and system prompt
Next I lock down the output format. The system prompt acts as a contract, forcing the model to return only the JSON keys I expect and nothing else.
SYSTEM_PROMPT = """You are a precise entity recognition engine.
Extract entities from the user text and return ONLY a JSON object with these keys:
- people: list of full names of persons mentioned
- organizations: list of companies, institutions, or agencies
- locations: list of cities, countries, or regions
- dates: list of explicit dates or time periods
- products: list of products, services, or technologies
Rules:
1. Return valid JSON with no markdown code fences.
2. Use empty lists for missing categories.
3. Do not add explanatory text outside the JSON."""
Step 3: Build the extraction function
I wrap the API call in a small function that sends the raw text and parses the JSON response. I keep the client call exactly as Oxlo.ai documents it.
import json
def extract_entities(text: str) -> dict:
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": text},
],
)
raw = response.choices[0].message.content
# Some models may return whitespace before the JSON
return json.loads(raw.strip())
Step 4: Add defensive parsing
LLM outputs can occasionally include a stray markdown fence or a leading sentence. I add a tiny cleanup layer that strips fences and falls back to an empty dict if parsing still fails.
import re
def safe_extract_entities(text: str) -> dict:
try:
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": text},
],
)
raw = response.choices[0].message.content.strip()
# Strip markdown fences if the model produced them
if raw.startswith("
```"):
raw = re.sub(r"^```
(?:json)?\s*", "", raw)
raw = re.sub(r"\s*
```
$", "", raw)
return json.loads(raw)
except Exception:
# In production you would log this; here we return a clean fallback
return {
"people": [],
"organizations": [],
"locations": [],
"dates": [],
"products": [],
}
Run it
Here is a messy paragraph that mixes people, companies, and products. I pass it to the extractor and print the result.
text = (
"In September 2023, Tim Cook announced that Apple would release the Vision Pro "
"in early 2024. The launch event was held in Cupertino, and journalists from "
"the BBC and Reuters covered it extensively."
)
result = safe_extract_entities(text)
print(json.dumps(result, indent=2))
Expected output:
{
"people": [
"Tim Cook"
],
"organizations": [
"Apple",
"BBC",
"Reuters"
],
"locations": [
"Cupertino"
],
"dates": [
"September 2023",
"early 2024"
],
"products": [
"Vision Pro"
]
}
Wrap up and next steps
The pipeline is now working end to end. Two concrete ways to push it further: wire the output into a SQLite table or vector store so you can query across documents, or expand the system prompt with domain-specific entity types like regulation_id or chemical_formula for scientific texts. If you are processing large batches, remember that Oxlo.ai bills per request, so chunking a 50-page report into one call is far cheaper than token-based pricing would allow. See the details at https://oxlo.ai/pricing.
Top comments (0)