DEV Community

shashank ms
shashank ms

Posted on

The Role of LLMs in Social Science Research

Social scientists spend weeks manually coding interview transcripts. In this tutorial, I will build a lightweight qualitative coding agent that reads transcripts, applies a deductive codebook, and extracts higher-level themes automatically using Oxlo.ai.

What you'll need

Step 1: Configure the Oxlo.ai client

We will use the OpenAI SDK pointed at Oxlo.ai. I am using Llama 3.3 70B because it follows detailed instructions and handles long context windows well.

from openai import OpenAI
import json

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

Step 2: Define the system prompt

The system prompt is the core of the agent. It instructs the model to act as a qualitative researcher, apply specific codes, and return structured JSON.

SYSTEM_PROMPT = """You are a qualitative research assistant. Your task is to analyze interview transcript segments and apply thematic coding.

Use the following deductive codebook:
- BARRIER: obstacles or challenges mentioned by the participant
- STRATEGY: coping mechanisms or solutions described
- IDENTITY: references to self-concept, roles, or social identity
- INSTITUTION: mentions of organizational, policy, or systemic factors

For each segment, return a JSON object with:
- "codes": a list of codes found, each with "label" (must be from the codebook), "evidence" (exact quoted text), and "memo" (1-sentence analytic note)
- "summary": one sentence summarizing the segment's main idea

Respond ONLY with valid JSON. Do not wrap the JSON in markdown fences."""

Step 3: Load and chunk transcripts

Real transcripts can be long, so I split on paragraph boundaries. This keeps speaker turns intact while respecting context limits.

def load_transcript(path):
    with open(path, "r", encoding="utf-8") as f:
        return f.read()

def chunk_text(text, max_chars=3000):
    chunks = []
    current = []
    current_len = 0

    for paragraph in text.split("\n\n"):
        para = paragraph.strip()
        if not para:
            continue
        if current_len + len(para) > max_chars and current:
            chunks.append("\n\n".join(current))
            current = [para]
            current_len = len(para)
        else:
            current.append(para)
            current_len += len(para)

    if current:
        chunks.append("\n\n".join(current))

    return chunks

Step 4: Run the coding analysis

This function sends each chunk to Oxlo.ai with the system prompt. I use JSON mode so the response is parseable without regex.

def code_segment(segment):
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": f"Analyze this transcript segment:\n\n{segment}"},
        ],
        response_format={"type": "json_object"},
    )
    raw = response.choices[0].message.content
    return json.loads(raw)

Step 5: Aggregate codes across chunks

After coding individual chunks, I merge duplicates and count frequencies to prepare for axial coding.

def aggregate_results(coded_segments):
    all_codes = []
    for seg in coded_segments:
        all_codes.extend(seg.get("codes", []))

    frequency = {}
    for code in all_codes:
        label = code["label"]
        frequency[label] = frequency.get(label, 0) + 1

    return {
        "total_codes": len(all_codes),
        "frequency": frequency,
        "details": all_codes
    }

Step 6: Generate thematic synthesis

Finally, I send the aggregated codes back to the model to produce higher-order themes, mimicking the axial coding phase of grounded theory.

def synthesize_themes(aggregated):
    prompt = f"""You are a qualitative researcher performing axial coding.
Given the following aggregated codes from an interview, identify 3 to 5 higher-order themes.
Return JSON with a list of themes. Each theme must have:
- "theme": theme name
- "definition": 1-sentence definition
- "related_codes": list of lower-level codes that belong to this theme
- "insight": 1-sentence analytic insight

Aggregated codes: {json.dumps(aggregated["details"], indent=2)}"""

    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": "You return only valid JSON."},
            {"role": "user", "content": prompt},
        ],
        response_format={"type": "json_object"},
    )
    return json.loads(response.choices[0].message.content)

Run it

This script ties the pipeline together using a short interview excerpt. In production, point load_transcript at a real .txt file.

if __name__ == "__main__":
    transcript = """Interviewer: Can you describe a typical day at the clinic?

Participant: It is overwhelming. We are short-staffed every single shift, so I end up doing the work of two people. I try to create checklists to stay organized, but the system is broken. Sometimes I feel like I am the only one who cares about the patients here.

Interviewer: How do you cope with that feeling?

Participant: I remind myself that I became a nurse to help people. That identity keeps me going. I also talk to my supervisor, but the policies come from downtown and nobody listens."""

    segments = chunk_text(transcript, max_chars=2000)
    coded = [code_segment(s) for s in segments]
    aggregated = aggregate_results(coded)
    themes = synthesize_themes(aggregated)

    print("=== AGGREGATED CODES ===")
    print(json.dumps(aggregated["frequency"], indent=2))
    print("\n=== THEMES ===")
    print(json.dumps(themes, indent=2))

Example output:

=== AGGREGATED CODES ===
{
  "BARRIER": 3,
  "STRATEGY": 2,
  "IDENTITY": 1,
  "INSTITUTION": 2
}

=== THEMES ===
{
  "themes": [
    {
      "theme": "Systemic Overload",
      "definition": "The participant experiences chronic resource shortages and bureaucratic failure that prevent effective care.",
      "related_codes": ["BARRIER", "INSTITUTION"],
      "insight": "Structural deficits are framed as personal burden, suggesting a workplace culture that individualizes systemic problems."
    },
    {
      "theme": "Professional Identity as Resilience",
      "definition": "The participant draws on a sense of vocational identity to sustain motivation amid adversity.",
      "related_codes": ["IDENTITY", "STRATEGY"],
      "insight": "Personal identity acts as a compensatory mechanism when institutional support is absent."
    }
  ]
}

Wrap-up

You now have a working qualitative coding agent that turns raw transcripts into structured themes. Because Oxlo.ai charges per request rather than per token, you can rerun this pipeline every time you refine your codebook without watching costs scale with transcript length.

Two concrete next steps. First, swap in qwen-3-32b via Oxlo.ai if you are working with multilingual fieldwork, since it handles mixed-language transcripts well. Second, add the BGE-Large embedding endpoint on Oxlo.ai to cluster uncoded segments and discover inductive codes you did not anticipate.

Top comments (0)