We are going to build a note-to-report generator that turns raw bullet points into structured Markdown documentation. I use this to clean up meeting notes and research scribbles without spending time on formatting. The tool runs on Oxlo.ai, where request-based pricing means long inputs do not inflate the cost, so I never worry about pasting in a full page of messy notes.
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
Step 1: Configure the Oxlo.ai client
I always start by verifying the connection with a small ping. This confirms my key and the base URL are correct before I build any logic around it.
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": "Say 'Connection OK' and nothing else."},
],
)
print(response.choices[0].message.content)
Step 2: Define the system prompt
The system prompt is the only place where I describe the tone, structure, and constraints. Keeping it in a dedicated constant makes it easy to tweak without touching business logic.
SYSTEM_PROMPT = """You are a technical documentation assistant.
Your job is to convert raw, unordered notes into a well-structured Markdown report.
The report must contain exactly these sections: Summary, Key Points, Action Items, and Risks.
Use professional but concise language. Do not add information that is not present in the notes.
Output strictly as a JSON object with keys: summary, key_points, action_items, and risks."""
Step 3: Assemble the user message
I wrap the raw notes inside a clear instruction so the model knows where the data starts and ends. This reduces formatting errors in the generated output.
def build_user_message(title, raw_notes):
notes_text = "\n".join(f"- {line}" for line in raw_notes if line.strip())
return f"""Generate a structured report for the following meeting notes.
Title: {title}
Raw Notes:
{notes_text}
Return the report as the JSON object described in your instructions."""
Step 4: Generate structured output with JSON mode
By setting response_format to JSON mode, I get parseable output without begging the model to follow format in the prompt. I use Llama 3.3 70B here because it handles instruction following reliably for long-form generation.
import json
def generate_report(title, raw_notes):
user_message = build_user_message(title, raw_notes)
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
response_format={"type": "json_object"},
)
content = response.choices[0].message.content
return json.loads(content)
Step 5: Render and save Markdown
The final step converts the JSON structure into readable Markdown and writes it to disk. I keep this separate from the API call so I can swap templates later without regenerating.
def render_markdown(title, report_json):
lines = [
f"# {title}",
"",
"## Summary",
report_json.get("summary", ""),
"",
"## Key Points",
]
for point in report_json.get("key_points", []):
lines.append(f"- {point}")
lines.extend([
"",
"## Action Items",
])
for item in report_json.get("action_items", []):
lines.append(f"- [ ] {item}")
lines.extend([
"",
"## Risks",
])
for risk in report_json.get("risks", []):
lines.append(f"- {risk}")
return "\n".join(lines)
def save_report(title, raw_notes, filename="report.md"):
data = generate_report(title, raw_notes)
md = render_markdown(title, data)
with open(filename, "w", encoding="utf-8") as f:
f.write(md)
print(f"Report saved to {filename}")
return md
Run it
Here is a real set of messy notes from a project sync I had last week. I pass them into the pipeline and print the result.
RAW_NOTES = [
"backend migration delayed by 3 days, waiting on db schema review",
"need to update auth middleware before next release",
"qwen 3 32b tested well on multilingual prompts, consider for v2",
"marketing wants api examples by friday",
"risk: if schema review slips, whole sprint is blocked",
"action: schedule pairing session with devops on tuesday",
]
if __name__ == "__main__":
output = save_report(
title="Sprint 24 Sync",
raw_notes=RAW_NOTES,
filename="sprint_24_report.md"
)
print("\n--- GENERATED REPORT ---\n")
print(output)
Example output:
# Sprint 24 Sync ## Summary The sprint is facing a potential delay due to a pending database schema review. Several technical and coordination tasks need attention before the next release. ## Key Points - Backend migration is delayed by three days pending database schema review. - Authentication middleware requires an update before the next release. - Qwen 3 32B demonstrated strong multilingual performance and is under consideration for version two. - Marketing has requested API examples by Friday. ## Action Items - [ ] Schedule a pairing session with DevOps on Tuesday. - [ ] Prepare API examples for marketing by Friday. - [ ] Complete auth middleware update before next release. ## Risks - If the schema review is delayed further, the entire sprint will be blocked.
Wrap-up
This generator is already saving me an hour each week. Two concrete ways to extend it: wire in Oxlo.ai's streaming responses to watch long reports render line by line, or batch process an entire directory of note files by looping over .txt inputs and writing .md outputs. Because Oxlo.ai uses flat per-request pricing, running it on a hundred long note files stays predictable. You can see the details at https://oxlo.ai/pricing.
Top comments (0)