DEV Community

shashank ms
shashank ms

Posted on

Building a Semantic Role Labeling Tool with LLM

Semantic role labeling identifies who did what to whom in a sentence, a task that usually requires heavy linguistic pipelines. We will build a lightweight SRL tool that calls an LLM to extract predicates and arguments into structured JSON, useful for prototyping information extraction or generating training data without maintaining a full NLP stack. I run this on Oxlo.ai because its flat per-request pricing keeps costs predictable even when I pass in long paragraphs or detailed few-shot prompts.

What you'll need

Step 1: Configure the client

I start by instantiating the OpenAI-compatible client pointing at Oxlo.ai. I use llama-3.3-70b for reliable instruction following on structured JSON tasks.

import os
from openai import OpenAI

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

Step 2: Write the system prompt

The prompt is the schema. I define PropBank-style roles and enforce JSON output so the model returns predictable objects I can parse without regex.

SYSTEM_PROMPT = """You are a semantic role labeling engine. Analyze the input sentence and return a JSON object describing every predicate and its semantic arguments.

Follow PropBank-style conventions:
- ARG0: agent or causer
- ARG1: patient or theme
- ARG2: beneficiary, instrument, or attribute
- ARGM-TMP: temporal modifier
- ARGM-LOC: locative modifier
- ARGM-MNR: manner modifier
- ARGM-NEG: negation marker

Output format:
{
  "sentence": "<original sentence>",
  "predicates": [
    {
      "predicate": "<predicate word>",
      "arguments": [
        {"role": "ARG0", "text": "<exact span>"},
        {"role": "ARG1", "text": "<exact span>"}
      ],
      "modifiers": [
        {"type": "ARGM-TMP", "text": "<exact span>"}
      ]
    }
  ]
}

Rules:
- Include every verbal predicate.
- Use exact text spans from the sentence.
- If a role is missing, omit it. Do not invent information.
- Return only the JSON object."""

Step 3: Build the extractor

I wrap the call in a small function that sends the sentence to Oxlo.ai and parses the JSON response. Setting response_format to json_object reduces formatting errors.

import json

def extract_srl(sentence: str, model: str = "llama-3.3-70b"):
    response = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": sentence},
        ],
        response_format={"type": "json_object"},
    )
    raw = response.choices[0].message.content
    return json.loads(raw)

Run it

Here is how I call the finished tool on a couple of sentences and what the output looks like.

if __name__ == "__main__":
    sentences = [
        "The engineer deployed the fix to production yesterday.",
        "Maria gave a book to her colleague."
    ]

    for s in sentences:
        result = extract_srl(s)
        print(json.dumps(result, indent=2))

Example output for the first sentence:

{
  "sentence": "The engineer deployed the fix to production yesterday.",
  "predicates": [
    {
      "predicate": "deployed",
      "arguments": [
        {"role": "ARG0", "text": "The engineer"},
        {"role": "ARG1", "text": "the fix"},
        {"role": "ARG2", "text": "to production"}
      ],
      "modifiers": [
        {"type": "ARGM-TMP", "text": "yesterday"}
      ]
    }
  ]
}

Next steps

Wire this into an async batch processor using asyncio to annotate entire corpora, or evaluate the extracted spans against PropBank gold data to measure overlap. If you move to paragraph-level SRL with cross-sentence coreference, Oxlo.ai stays cheap because the per-request price does not grow with prompt length. See https://oxlo.ai/pricing for plan details.

Top comments (0)