DEV Community

shashank ms
shashank ms

Posted on

Building a Part-of-Speech Tagging Tool with LLM

Part-of-speech tagging is still a pain for quick scripts and agentic pipelines. Instead of installing spaCy or NLTK, we will build a lightweight POS tagger that calls an LLM through Oxlo.ai and returns structured tags for any English sentence. The whole thing is under fifty lines of Python.

What you'll need

  • Python 3.10+
  • The OpenAI SDK installed with pip install openai
  • An Oxlo.ai API key from https://portal.oxlo.ai

Step 1: Configure the Oxlo.ai client

I keep my key in an environment variable and initialize the client exactly like the OpenAI SDK, just pointing the base URL at Oxlo.ai.

import os
from openai import OpenAI

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

Step 2: Lock in the system prompt

The prompt below forces the model to return only a raw JSON array of objects, each containing the exact word and its Penn Treebank tag. I use llama-3.3-70b because it follows instructions tightly, and on Oxlo.ai the request is one flat charge regardless of prompt length.

SYSTEM_PROMPT = """You are a part-of-speech tagger.
Given a user sentence, return a JSON array of objects.
Each object must have exactly two keys:
  "word": the exact token from the input, punctuation included
  "tag": the Penn Treebank tag

Rules:
1. Do not alter capitalization.
2. Treat punctuation as separate tokens.
3. Output ONLY the raw JSON array. No markdown, no explanation.

Example:
Input: Hello, world!
Output: [{"word": "Hello", "tag": "UH"}, {"word": ",", "tag": ","}, {"word": "world", "tag": "NN"}, {"word": "!", "tag": "."}]
"""

Step 3: Build the tagger function

This function sends the sentence to Oxlo.ai, enables JSON mode for deterministic output, and parses the result. I set the temperature low to keep tags consistent.

import json

def tag_sentence(text: str, model: str = "llama-3.3-70b") -> list[dict]:
    response = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": text},
        ],
        response_format={"type": "json_object"},
        temperature=0.1,
    )

    raw = response.choices[0].message.content
    parsed = json.loads(raw)

    if isinstance(parsed, dict):
        return list(parsed.values())[0]
    return parsed

Step 4: Add a CLI wrapper and printer

A small pretty-printer makes the output readable, and the main block gives us a one-shot test.

def print_tags(tagged: list[dict]) -> None:
    print("TOKEN".ljust(15) + "TAG".ljust(6))
    print("-" * 22)
    for item in tagged:
        print(item["word"].ljust(15) + item["tag"].ljust(6))

if __name__ == "__main__":
    sample = "Oxlo.ai offers flat per-request pricing for LLM inference."
    result = tag_sentence(sample)
    print_tags(result)

Run it

Save the script as pos_tagger.py, export your key, and run it.

$ export OXLO_API_KEY="sk-oxlo.ai-..."
$ python pos_tagger.py

You should see something like this:

TOKEN          TAG   
----------------------
Oxlo.ai        NNP   
offers         VBZ   
flat           JJ    
per-request    JJ    
pricing        NN    
for            IN    
LLM            NNP   
inference      NN    
.              .     

Wrap-up and next steps

Because Oxlo.ai uses flat per-request pricing, sending a long paragraph costs the same as a single word. That makes this pattern practical for bulk document processing. See https://oxlo.ai/pricing if you want to scale up.

Two concrete ways to extend this:

  • Pipe the JSON output into a CoNLL-U formatter so you can drop tags into Universal Dependencies pipelines.
  • Swap the model to kimi-k2.6 or deepseek-v3.2 for multilingual POS tagging without changing any client code.

Top comments (0)