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.6ordeepseek-v3.2for multilingual POS tagging without changing any client code.
Top comments (0)