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
- An Oxlo.ai API key from https://portal.oxlo.ai
- Python 3.10 or newer
- The OpenAI SDK:
pip install openai
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)