We're building an automated essay feedback agent that grades student submissions against a custom rubric and returns structured, actionable critiques. It gives teachers a scalable way to provide detailed feedback without spending hours on every assignment. I'll walk through the exact Python pipeline I put together using Oxlo.ai's OpenAI-compatible API.
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: Scaffold the client and test connectivity
I always start by verifying the endpoint responds. This snippet initializes the Oxlo.ai client and sends a lightweight health-check prompt.
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 'Connection OK' and nothing else."},
],
)
print(response.choices[0].message.content)
Step 2: Define the rubric and system prompt
The agent needs strict instructions to output valid JSON and stick to the rubric. I use a system prompt that locks the evaluation criteria and response schema. This keeps outputs deterministic enough to parse safely.
SYSTEM_PROMPT = """You are an experienced high-school writing instructor. Evaluate the student's essay paragraph against the following rubric and respond ONLY with a JSON object.
Rubric:
- thesis_clarity: Does the paragraph contain a clear, arguable thesis? (0-5)
- evidence_use: Does it use specific evidence to support claims? (0-5)
- analysis_depth: Does it explain how evidence supports the thesis? (0-5)
- grammar_style: Are there few grammatical errors and is the style appropriate? (0-5)
Response schema:
{
"scores": {
"thesis_clarity": int,
"evidence_use": int,
"analysis_depth": int,
"grammar_style": int
},
"feedback": {
"strengths": "string",
"improvements": "string"
}
}
Be concise. Do not include markdown code fences in the JSON."""
Step 3: Build the grading function
Now I wrap the API call in a clean function that accepts a student submission and returns parsed scores and feedback. I use Llama 3.3 70B here because it follows structured instructions reliably on Oxlo.ai.
import json
def grade_submission(student_text: str):
user_message = f"Student submission:\n{student_text}"
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
)
raw = response.choices[0].message.content.strip()
# Handle occasional markdown fences
if raw.startswith("
```"):
raw = raw.split("```
")[1].replace("json", "").strip()
return json.loads(raw)
Step 4: Add Socratic hint generation for weak areas
If a student scores below 3 on any criterion, I want to give them a nudge without handing over the answer. I send a second request to a reasoning model. I like Qwen 3 32B on Oxlo.ai for this because it handles multilingual reasoning well, which matters if student work contains mixed language sources.
def generate_hint(student_text: str, weak_area: str):
hint_prompt = (
"You are a Socratic tutor. The student wrote the following paragraph. "
"They need to improve their " + weak_area + ". "
"Ask one guiding question that helps them discover what is missing. "
"Do not give the answer. Keep it under 20 words.\n\nStudent work:\n" + student_text
)
response = client.chat.completions.create(
model="qwen-3-32b",
messages=[
{"role": "system", "content": "You are a concise Socratic tutor."},
{"role": "user", "content": hint_prompt},
],
)
return response.choices[0].message.content.strip()
Step 5: Assemble the full pipeline
Finally, I tie the pieces together. The pipeline grades the submission, identifies weak areas, and appends targeted hints.
def full_feedback_pipeline(student_text: str):
result = grade_submission(student_text)
hints = []
for criterion, score in result["scores"].items():
if score < 3:
hint = generate_hint(student_text, criterion)
hints.append({"criterion": criterion, "hint": hint})
result["hints"] = hints
return result
# Example student paragraph
student_paragraph = (
"Photosynthesis is important for plants because they need it. "
"It happens in the leaves and uses sunlight. "
"This process is good for the environment and helps everyone."
)
output = full_feedback_pipeline(student_paragraph)
print(json.dumps(output, indent=2))
Run it
Running the pipeline against the sample paragraph produces structured feedback and targeted hints. Here is the exact output I got on my last run.
{
"scores": {
"thesis_clarity": 2,
"evidence_use": 1,
"analysis_depth": 1,
"grammar_style": 4
},
"feedback": {
"strengths": "The paragraph uses grammatically correct sentences and maintains an appropriate academic tone.",
"improvements": "Add a specific, arguable claim about photosynthesis. Use concrete evidence, such as data or examples, and explain the mechanism rather than just stating it occurs."
},
"hints": [
{
"criterion": "thesis_clarity",
"hint": "What specific claim are you making about why photosynthesis matters beyond general importance?"
},
{
"criterion": "evidence_use",
"hint": "Can you name a specific study, measurement, or example that demonstrates how photosynthesis benefits the environment?"
},
{
"criterion": "analysis_depth",
"hint": "How does the transformation of sunlight into chemical energy actually support your claim?"
}
]
}
Wrap-up
Two concrete ways to extend this next. First, wire the pipeline into an LMS webhook so submissions trigger automatic feedback on receipt. Second, batch-process an entire class roster overnight. Because Oxlo.ai uses request-based pricing, long student essays do not inflate your bill, which makes batch jobs predictable. You can explore the exact plan breakdown at https://oxlo.ai/pricing.
Top comments (0)