DEV Community

shashank ms
shashank ms

Posted on

Unlocking the Potential of LLMs for Academic Writing

We are building an academic writing assistant on Oxlo.ai that generates structured paper sections, critiques its own output, and exports a clean Markdown draft. It is useful for graduate students and researchers who need to iterate quickly on long-form technical content, because Oxlo.ai's flat per-request pricing does not penalize long contexts or multi-step refinement loops.

What you'll need

  • Python 3.10 or newer
  • The OpenAI SDK: pip install openai
  • An Oxlo.ai API key from https://portal.oxlo.ai (the free tier gives you 60 requests per day, enough to prototype)
  • A paper topic and three to five bullet points to seed the draft

Step 1: Configure the Oxlo.ai client

First, we initialize the OpenAI-compatible client pointing to Oxlo.ai. I use Oxlo.ai here because the flat per-request pricing (https://oxlo.ai/pricing) does not scale with input length, so passing a long outline or an entire previous section back into the context window costs the same as a one-line ping.

from openai import OpenAI

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

response = client.chat.completions.create(
    model="llama-3.3-70b",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Say hello"},
    ],
)
print("Connected:", response.choices[0].message.content)

Step 2: Write the academic system prompt

Next, we define the system prompt. I keep it strict about tone, citation style, and output format so the model does not drift into marketing speak or omit references.

SYSTEM_PROMPT = """You are a scholarly writing assistant. Observe these rules:
- Write in formal academic English.
- Use APA 7th edition in-text citations.
- Return structured JSON when requested.
- Flag uncertain claims with [CITE NEEDED].
- Avoid filler phrases such as 'in recent years' or 'with the development of'."""

Step 3: Generate a structured section

Now we build the draft generator. It accepts a section name, paper title, and bullet outline, then returns a JSON object with the title, body paragraphs, and a reference list. Using JSON mode keeps the downstream parser reliable.

import json

def draft_section(section_name, topic, outline_points):
    user_msg = (
        f"Write the {section_name} for a paper titled '{topic}'. "
        f"Cover these points: {json.dumps(outline_points)}. "
        "Return valid JSON with keys: section_title, body_paragraphs (list of strings), citations (list of strings)."
    )

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

Step 4: Add a self-critique loop

A first draft is rarely good enough. This pair of functions sends the draft to Kimi K2.6 for a harsh critique, then feeds that feedback back to Llama 3.3 70B for a rewrite. Because Oxlo.ai uses flat per-request pricing (https://oxlo.ai/pricing), running this two-pass refinement on a long input costs the same as two short prompts.

def critique_section(draft_json):
    user_msg = (
        "Critique the following introduction for clarity, argument strength, and citation quality. "
        "Be specific and harsh. Return a JSON object with a single key 'critique_points' containing a list of strings.\n\n"
        f"{json.dumps(draft_json)}"
    )

    response = client.chat.completions.create(
        model="kimi-k2.6",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_msg},
        ],
        response_format={"type": "json_object"},
    )
    return json.loads(response.choices[0].message.content)["critique_points"]


def rewrite_section(draft_json, critique_points):
    user_msg = (
        "Rewrite the section below based on the critique. Improve flow and fix weak citations. "
        "Return valid JSON with keys: section_title, body_paragraphs, citations.\n\n"
        f"Original: {json.dumps(draft_json)}\n\n"
        f"Critique: {json.dumps(critique_points)}"
    )

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

Step 5: Format and export to Markdown

Finally, we serialize the result to Markdown so you can open it in any editor or paste it into Overleaf. I also append the critique points as revision notes so nothing gets lost.

def export_markdown(topic, final_json, critique_points, filename="draft.md"):
    lines = [
        f"# {topic}",
        "",
        f"## {final_json['section_title']}",
        "",
    ]
    for para in final_json["body_paragraphs"]:
        lines.append(para)
        lines.append("")

    if final_json.get("citations"):
        lines.append("## References")
        lines.append("")
        for cite in final_json["citations"]:
            lines.append(f"- {cite}")
        lines.append("")

    if critique_points:
        lines.append("## Revision Notes")
        lines.append("")
        for point in critique_points:
            lines.append(f"- {point}")
        lines.append("")

    with open(filename, "w", encoding="utf-8") as f:
        f.write("\n".join(lines))

    print(f"Saved to {filename}")

Run it

This main block ties everything together. It drafts an introduction, critiques it, rewrites it, and writes the file.

if __name__ == "__main__":
    TOPIC = "Federated Learning for Healthcare Data Privacy"
    OUTLINE = [
        "Define federated learning and its core privacy promise.",
        "Contrast with traditional centralized training in clinical settings.",
        "Introduce the privacy-utility trade-off as the central research question.",
    ]

    print("Drafting...")
    draft = draft_section("Introduction", TOPIC, OUTLINE)

    print("Critiquing...")
    points = critique_section(draft)
    for p in points:
        print(" -", p)

    print("Rewriting...")
    final = rewrite_section(draft, points)

    export_markdown(TOPIC, final, points)

Example output:

Connected: Hello
Drafting...
Critiquing...
 - The privacy-utility trade-off is stated but not quantified.
 - Paragraph 2 repeats the definition without advancing the argument.
 - All three citations are marked [CITE NEEDED].
Rewriting...
Saved to draft.md

Preview of draft.md:
# Federated Learning for Healthcare Data Privacy

## Introduction

Federated learning enables model training on distributed clinical data without centralizing sensitive patient records (McMahan et al., 2017). Unlike traditional centralized pipelines, which require explicit data sharing agreements and expose institutions to breach risk, federated approaches aggregate gradients rather than raw samples. However, this architectural shift introduces a privacy-utility trade-off that remains poorly characterized in heterogeneous medical environments [CITE NEEDED].

## References

- McMahan, B., Moore, E., Ramage, D., Hampson, S., & y Arcas, B. A. (2017). Communication-efficient learning of deep networks from decentralized data. In Artificial intelligence and statistics (pp. 1273-1282). PMLR.

## Revision Notes

- The privacy-utility trade-off is stated but not quantified.
- Paragraph 2 repeats the definition without advancing the argument.
- All three citations are marked [CITE NEEDED].

Next steps

Swap in DeepSeek V4 Flash or Kimi K2.6 to pass an entire 50-page manuscript through the 1M context window for a global coherence check in a single request. You could also add vision support with Kimi VL A3B or Gemma 3 27B to describe figures and equations from scanned PDFs, then feed that text back into the writing loop.

Top comments (0)