We're building a dependency parser that uses an LLM to extract grammatical relationships from raw text. This gives you structured syntax trees without maintaining spaCy pipelines for every language. It is especially useful for low-resource languages or domain-specific sentences where traditional parsers fail.
What you'll need
Python 3.10 or newer, the openai SDK (pip install openai), and an Oxlo.ai API key from https://portal.oxlo.ai. Oxlo.ai is fully OpenAI SDK compatible, so the setup is a drop-in replacement.
Step 1: Configure the Oxlo.ai client
First, point the OpenAI client at Oxlo.ai. I use Llama 3.3 70B here because it follows structured instructions reliably and runs with no cold starts on Oxlo.ai.
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key="YOUR_OXLO_API_KEY"
)
Step 2: Define the dependency schema
I enforce JSON output so the model returns a predictable object. The schema follows Universal Dependencies v2: every token gets an ID, text, lemma, UPOS tag, head ID, and dependency relation.
# Expected JSON structure returned by the model
schema_hint = {
"tokens": [
{
"id": 1,
"text": "word",
"lemma": "word",
"upos": "NOUN",
"head": 0,
"deprel": "root"
}
]
}
Step 3: Write the system prompt
The system prompt teaches the model UD conventions and forbids extra commentary. Keeping it in a constant makes it easy to tweak later.
SYSTEM_PROMPT = """You are a dependency parser.
Analyze the input sentence and return a JSON object with a single key "tokens".
Each token must have these fields:
- id: 1-indexed position
- text: exact surface form
- lemma: dictionary form
- upos: Universal Part-of-Speech tag
- head: id of the governor token (0 if this token is the root)
- deprel: Universal Dependency relation to the head
Follow Universal Dependencies v2 guidelines. Include every word and punctuation mark as a separate token. If the token is the root, set head to 0 and deprel to "root". Return only the JSON object, with no markdown formatting."""
Step 4: Build the parsing function
This function sends the sentence to Oxlo.ai with response_format set to JSON mode. I keep temperature low to reduce hallucinated labels.
def parse_sentence(sentence: str):
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": sentence},
],
response_format={"type": "json_object"},
temperature=0.1,
)
content = response.choices[0].message.content
return json.loads(content)
Step 5: Add a tree formatter
Raw JSON is hard to read. This helper walks the head links and prints an indented tree so you can verify the structure at a glance.
def print_tree(tokens):
token_map = {t["id"]: t for t in tokens}
roots = [t for t in tokens if t["head"] == 0]
def walk(node, depth=0):
indent = " " * depth
rel = node.get("deprel", "root")
print(f"{indent}- {node['text']} ({node['upos']}, {rel})")
children = [t for t in tokens if t["head"] == node["id"]]
for child in children:
walk(child, depth + 1)
for root in roots:
walk(root)
Run it
Feed the parser a sentence with nested modifiers. Because Oxlo.ai charges per request rather than per token, a long sentence costs the same as a short one. You can see current plans on the pricing page.
sentence = "The quick brown fox jumps over the lazy dog."
result = parse_sentence(sentence)
print(json.dumps(result, indent=2))
print("\nTree view:")
print_tree(result["tokens"])
Example output:
{
"tokens": [
{"id": 1, "text": "The", "lemma": "the", "upos": "DET", "head": 4, "deprel": "det"},
{"id": 2, "text": "quick", "lemma": "quick", "upos": "ADJ", "head": 4, "deprel": "amod"},
{"id": 3, "text": "brown", "lemma": "brown", "upos": "ADJ", "head": 4, "deprel": "amod"},
{"id": 4, "text": "fox", "lemma": "fox", "upos": "NOUN", "head": 5, "deprel": "nsubj"},
{"id": 5, "text": "jumps", "lemma": "jump", "upos": "VERB", "head": 0, "deprel": "root"},
{"id": 6, "text": "over", "lemma": "over", "upos": "ADP", "head": 9, "deprel": "case"},
{"id": 7, "text": "the", "lemma": "the", "upos": "DET", "head": 9, "deprel": "det"},
{"id": 8, "text": "lazy", "lemma": "lazy", "upos": "ADJ", "head": 9, "deprel": "amod"},
{"id": 9, "text": "dog", "lemma": "dog", "upos": "NOUN", "head": 5, "deprel": "obl"},
{"id": 10, "text": ".", "lemma": ".", "upos": "PUNCT", "head": 5, "deprel": "punct"}
]
}
Tree view:
- jumps (VERB, root)
- fox (NOUN, nsubj)
- The (DET, det)
- quick (ADJ, amod)
- brown (ADJ, amod)
- dog (NOUN, obl)
- over (ADP, case)
- the (DET, det)
- lazy (ADJ, amod)
- . (PUNCT, punct)
Wrap-up and next steps
The parser works, but it is just a starting point. Two concrete directions you can take next:
-
Batch processing: Wrap
parse_sentencein an async loop and process a full dataset. Oxlo.ai's request-based pricing means a 50-token sentence and a 5,000-token paragraph cost the same per call, which makes large-scale parsing predictable. -
Multilingual parsing: Swap the model to
qwen-3-32bon Oxlo.ai. Qwen 3 handles dozens of languages well, so you can parse sentences in Chinese, Arabic, or Hindi with the same prompt and no pipeline changes.
Top comments (0)