DEV Community

shashank ms
shashank ms

Posted on

Optimizing Grant Writing with LLM: Tips and Tricks

We are building a grant writing assistant that turns rough project notes into structured, funder-ready proposal drafts. It helps research teams and nonprofits cut first-draft time from days to minutes. I shipped a version of this pipeline last quarter on Oxlo.ai, and the flat per-request pricing kept long-context expansion passes affordable even when we fed in prior awards and full RFP text.

What you'll need

Step 1: Scaffold the client and test the endpoint

I never write prompt logic against a dead connection. Start by instantiating the client and sending a single confirmation message so you know the base URL and key are healthy.

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": "user", "content": "Reply with exactly: Oxlo.ai connection OK"},
    ],
)

print(response.choices[0].message.content)

Step 2: Craft the system prompt

The system prompt is the entire product. It tells the model how to behave like a senior grant writer, which sections to emit, and where to flag missing data so the human team knows what to fill in.

from openai import OpenAI

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

SYSTEM_PROMPT = """You are a senior grant writer who specializes in turning rough notes into structured federal proposals.

Follow these rules:
1. Output a draft with these sections: Project Summary, Intellectual Merit, Broader Impacts, Research Plan, Timeline, and Budget Justification.
2. Use active voice and concrete metrics.
3. If a required detail is missing, flag it inline with [NEEDS DATA: description].
4. Mirror the funder's language and priorities.
5. Format everything in Markdown.

Keep the tone professional, concise, and reviewer-friendly."""

Step 3: Build the draft generator

Now we wrap the prompt in a function that accepts raw notes and a funder name. I use llama-3.3-70b here because it follows long formatting instructions reliably and stays on template.

from openai import OpenAI

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

SYSTEM_PROMPT = """You are a senior grant writer who specializes in turning rough notes into structured federal proposals.

Follow these rules:
1. Output a draft with these sections: Project Summary, Intellectual Merit, Broader Impacts, Research Plan, Timeline, and Budget Justification.
2. Use active voice and concrete metrics.
3. If a required detail is missing, flag it inline with [NEEDS DATA: description].
4. Mirror the funder's language and priorities.
5. Format everything in Markdown.

Keep the tone professional, concise, and reviewer-friendly."""

def generate_draft(notes: str, funder: str = "NSF") -> str:
    user_message = f"Funder: {funder}\n\nRaw notes:\n{notes}"
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
        ],
        temperature=0.3,
    )
    return response.choices[0].message.content

Step 4: Add a compliance checker

A draft is useless if reviewers reject it for missing evaluation plans or weak broader impacts. I run a second pass with kimi-k2.6 to audit the draft against common rejection patterns and return a punch list.

from openai import OpenAI

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

SYSTEM_PROMPT = """You are a senior grant writer who specializes in turning rough notes into structured federal proposals.

Follow these rules:
1. Output a draft with these sections: Project Summary, Intellectual Merit, Broader Impacts, Research Plan, Timeline, and Budget Justification.
2. Use active voice and concrete metrics.
3. If a required detail is missing, flag it inline with [NEEDS DATA: description].
4. Mirror the funder's language and priorities.
5. Format everything in Markdown.

Keep the tone professional, concise, and reviewer-friendly."""

def generate_draft(notes: str, funder: str = "NSF") -> str:
    user_message = f"Funder: {funder}\n\nRaw notes:\n{notes}"
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
        ],
        temperature=0.3,
    )
    return response.choices[0].message.content

def compliance_check(draft: str) -> str:
    audit_prompt = """You are a grant review panelist. Review the draft below and list every weakness as a bullet.
Score the following explicitly:
1. Clarity of hypothesis or aims
2. Strength of broader impacts
3. Feasibility of timeline and budget
4. Specificity of the evaluation plan

Be concise and actionable."""
    response = client.chat.completions.create(
        model="kimi-k2.6",
        messages=[
            {"role": "system", "content": audit_prompt},
            {"role": "user", "content": draft},
        ],
        temperature=0.2,
    )
    return response.choices[0].message.content

Step 5: Assemble the final pipeline and save to disk

Finally, we stitch the draft and the audit into one markdown file so the human team has both the narrative and the revision list in a single place.

from openai import OpenAI

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

SYSTEM_PROMPT = """You are a senior grant writer who specializes in turning rough notes into structured federal proposals.

Follow these rules:
1. Output a draft with these sections: Project Summary, Intellectual Merit, Broader Impacts, Research Plan, Timeline, and Budget Justification.
2. Use active voice and concrete metrics.
3. If a required detail is missing, flag it inline with [NEEDS DATA: description].
4. Mirror the funder's language and priorities.
5. Format everything in Markdown.

Keep the tone professional, concise, and reviewer-friendly."""

def generate_draft(notes: str, funder: str = "NSF") -> str:
    user_message = f"Funder: {funder}\n\nRaw notes:\n{notes}"
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
        ],
        temperature=0.3,
    )
    return response.choices[0].message.content

def compliance_check(draft: str) -> str:
    audit_prompt = """You are a grant review panelist. Review the draft below and list every weakness as a bullet.
Score the following explicitly:
1. Clarity of hypothesis or aims
2. Strength of broader impacts
3. Feasibility of timeline and budget
4. Specificity of the evaluation plan

Be concise and actionable."""
    response = client.chat.completions.create(
        model="kimi-k2.6",
        messages=[
            {"role": "system", "content": audit_prompt},
            {"role": "user", "content": draft},
        ],
        temperature=0.2,
    )
    return response.choices[0].message.content

def save_proposal(notes: str, funder: str = "NSF", filename: str = "grant_draft.md"):
    draft = generate_draft(notes, funder)
    audit = compliance_check(draft)
    with open(filename, "w") as f:
        f.write(f"# Grant Draft ({funder})\n\n")
        f.write(draft)
        f.write("\n\n---\n\n")
        f.write("## Review Panel Feedback\n\n")
        f.write(audit)
    print(f"Saved to {filename}")
    return draft, audit

Run it

Here is a realistic set of raw notes from an environmental science PI. Calling save_proposal produces a ready-to-edit draft and a review checklist.

from openai import OpenAI

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

SYSTEM_PROMPT = """You are a senior grant writer who specializes in turning rough notes into structured federal proposals.

Follow these rules:
1. Output a draft with these sections: Project Summary, Intellectual Merit, Broader Impacts, Research Plan, Timeline, and Budget Justification.
2. Use active voice and concrete metrics.
3. If a required detail is missing, flag it inline with [NEEDS DATA: description].
4. Mirror the funder's language and priorities.
5. Format everything in Markdown.

Keep the tone professional, concise, and reviewer-friendly."""

def generate_draft(notes: str, funder: str = "NSF") -> str:
    user_message = f"Funder: {funder}\n\nRaw notes:\n{notes}"
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
        ],
        temperature=0.3,
    )
    return response.choices[0].message.content

def compliance_check(draft: str) -> str:
    audit_prompt = """You are a grant review panelist. Review the draft below and list every weakness as a bullet.
Score the following explicitly:
1. Clarity of hypothesis or aims
2. Strength of broader impacts
3. Feasibility of timeline and budget
4. Specificity of the evaluation plan

Be concise and actionable."""
    response = client.chat.completions.create(
        model="kimi-k2.6",
        messages=[
            {"role": "system", "content": audit_prompt},
            {"role": "user", "content": draft},
        ],
        temperature=0.2,
    )
    return response.choices[0].message.content

def save_proposal(notes: str, funder: str = "NSF", filename: str = "grant_draft.md"):
    draft = generate_draft(notes, funder)
    audit = compliance_check(draft)
    with open(filename, "w") as f:
        f.write(f"# Grant Draft ({funder})\n\n")
        f.write(draft)
        f.write("\n\n---\n\n")
        f.write("## Review Panel Feedback\n\n")
        f.write(audit)
    print(f"Saved to {filename}")
    return draft, audit

RAW_NOTES = """
Title: Low-cost sensors for PFAS detection in groundwater
We want to build a 50 sensor network in Michigan.
Team: Dr. Smith (chem), Dr. Lee (hydro), community partners in Flint.
Budget around 800k over 3 years.
Need to train local high schoolers to maintain sensors.
Novelty: existing sensors cost 10k each, ours targets 200.
"""

if __name__ == "__main__":
    draft, audit = save_proposal(RAW_NOTES, funder="NSF", filename="nsf_grant.md")
    print("\n=== AUDIT ===\n")
    print(audit)

Example output:

Saved to nsf_grant.md

=== AUDIT ===

- Broader impacts section mentions student training but lacks specific partner letters of commitment.
- Evaluation plan does not define quantitative metrics for sensor uptime or student retention.
- Timeline is missing a Year 1 manufacturing milestone for the 200 dollar sensor prototype.
- Budget justification does not explain the cost model for community partner coordination.

Wrap-up

Two concrete ways to extend this.

First, add retrieval from a vector store of past funded grants so the assistant can mirror winning language. Oxlo.ai's request-based pricing makes it cheap to include those long reference documents in every call without ballooning token costs.

Second, wire the script into a lightweight web UI with Streamlit so non-technical staff can paste notes and download the markdown without touching Python.

Top comments (0)