DEV Community

shashank ms
shashank ms

Posted on

Using LLMs for Grant Writing: A Guide

We are going to build a grant writing assistant that converts rough project notes into structured NIH-style proposal sections. It is designed for researchers and non-profit staff who need to generate repetitive, compliance-heavy drafts without starting from scratch every time.

What you'll need

Step 1: Set up the Oxlo.ai client

First, we initialize the OpenAI SDK pointing at Oxlo.ai and verify the connection with a quick health check. I use llama-3.3-70b here because it handles long instructions and formatting reliably.

from openai import OpenAI

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

# Quick connectivity test
test = client.chat.completions.create(
    model="llama-3.3-70b",
    messages=[{"role": "user", "content": "Say OK"}],
)
assert "OK" in test.choices[0].message.content
print("Client ready")

Step 2: Define the system prompt

This prompt frames the model as a senior grant writer. It enforces structure, limits jargon, and requires concrete milestones. Keep this editable. Swap in your own funder guidelines later.

SYSTEM_PROMPT = """You are a senior scientific grant writer with 15 years of NIH and NSF experience.
Your job is to turn rough project notes into structured proposal sections.

Rules:
- Write in active voice.
- Define all acronyms on first use.
- Include 3 specific aims with measurable milestones.
- Limit the abstract to 300 words.
- Flag any missing budget or compliance details as [NEEDS DATA].
- Do not invent statistics; if none are provided, write [CITE]."""

Step 3: Generate the Specific Aims page

We feed the system prompt plus raw notes to the model and return a formatted Specific Aims section. This is the core of the script.

def generate_specific_aims(project_notes: str) -> str:
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": f"Write a Specific Aims page for the following project:\n\n{project_notes}"},
        ],
        temperature=0.3,
    )
    return response.choices[0].message.content

# Example notes
notes = """
Title: AI-driven early detection of diabetic retinopathy in rural clinics.
Problem: Rural areas lack ophthalmologists. Current screening requires travel.
Approach: Deploy a lightweight CNN on edge devices for point-of-care screening.
Impact: Reduce time-to-diagnosis from 6 months to same-day.
Team: PI Dr. Smith (ophthalmology), Co-I Dr. Lee (ML).
Budget: $450k over 3 years.
"""

aims_draft = generate_specific_aims(notes)
print(aims_draft)

Step 4: Build a structured budget justification with JSON mode

Grant offices need line items, not prose. We use JSON mode to enforce a schema they can paste directly into an institutional spreadsheet. Oxlo.ai supports this through the standard OpenAI SDK argument.

import json

def generate_budget_json(project_notes: str) -> dict:
    response = client.chat.completions.create(
        model="qwen-3-32b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": f"Extract a 3-year budget justification from these notes and return valid JSON with keys: personnel, equipment, travel, other_direct_costs, total. Use integer dollars.\n\n{project_notes}"},
        ],
        response_format={"type": "json_object"},
        temperature=0.1,
    )
    return json.loads(response.choices[0].message.content)

budget = generate_budget_json(notes)
print(json.dumps(budget, indent=2))

Step 5: Add a critique loop

Before sending anything to a PI, I like to have the model review its own draft for clarity and compliance gaps. We run a second pass that outputs a bulleted list of fixes, then apply them.

def critique_draft(draft: str) -> str:
    response = client.chat.completions.create(
        model="kimi-k2.6",
        messages=[
            {"role": "system", "content": "You are a strict NIH review panel member. List concrete weaknesses and missing elements in the draft below. Be terse."},
            {"role": "user", "content": draft},
        ],
        temperature=0.2,
    )
    return response.choices[0].message.content

def refine_aims(draft: str, critique: str) -> str:
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "assistant", "content": draft},
            {"role": "user", "content": f"Revise the draft based on this critique:\n\n{critique}"},
        ],
        temperature=0.3,
    )
    return response.choices[0].message.content

critique = critique_draft(aims_draft)
aims_final = refine_aims(aims_draft, critique)
print("=== CRITIQUE ===")
print(critique)
print("\n=== FINAL AIMS ===")
print(aims_final)

Run it

Putting it together, the full script reads raw notes, writes the Specific Aims, builds a budget JSON, and self-revises. Because Oxlo.ai charges a flat rate per request, running this three-step pipeline on a long set of notes costs the same as a single-sentence ping, which matters when you are iterating on 20-page proposals.

if __name__ == "__main__":
    # 1. Aims
    aims = generate_specific_aims(notes)
    
    # 2. Budget
    budget = generate_budget_json(notes)
    
    # 3. Critique and refine
    critique = critique_draft(aims)
    aims_final = refine_aims(aims, critique)
    
    print("SPECIFIC AIMS")
    print(aims_final)
    print("\nBUDGET JUSTIFICATION")
    print(json.dumps(budget, indent=2))
    print("\nREVIEWER NOTES")
    print(critique)

Example output (abbreviated):

SPECIFIC AIMS
Project Summary: Diabetic retinopathy (DR) is the leading cause of blindness...
Specific Aim 1: Develop and validate a lightweight CNN...
Specific Aim 2: Deploy edge devices in 12 rural clinics...
Specific Aim 3: Conduct a prospective cohort study...

BUDGET JUSTIFICATION
{
  "personnel": 310000,
  "equipment": 85000,
  "travel": 25000,
  "other_direct_costs": 30000,
  "total": 450000
}

REVIEWER NOTES
- Milestones lack quantitative thresholds.
- No data management plan mentioned.
- Budget justification does not justify the 3-year timeline.

Wrap-up and next steps

This pipeline gives you a repeatable first draft, but it is still a starting point. Two concrete moves from here: integrate a document loader so the agent can ingest your institution's past funded grants as style references, or wrap the script in a FastAPI endpoint so your office of research can submit notes through a web form and get back a Word document.

If you are iterating on long proposals, Oxlo.ai's request-based pricing removes the token-meter anxiety that comes with stuffing full RFPs and prior award text into the context window.

Top comments (0)