DEV Community

shashank ms
shashank ms

Posted on

Using LLM for Entity Recognition: A Step-by-Step Guide

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-70b model 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)