Academy AI Assistant
RAG-based AI teaching assistant for LearnPress/WordPress, restricted to instructor-approved sources
by Academy Tech · github.com/a-babaei/academy-ai-assistant · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/a-babaei/academy-ai-assistant/archive/refs/heads/main.zipReadme
Academy AI Assistant
A custom WordPress plugin that adds a source-restricted AI teaching assistant to a LearnPress site. Built for academy-tech.ir to run entirely on standard WordPress hosting — no separate backend server, no managed vector database required.
It can:
- Answer student questions only from content an instructor has explicitly approved — nothing else, no general knowledge.
- Give hints on a student's practice/homework code submission without handing them the full solution.
- Grade practice submissions and open-ended quiz answers against an approved rubric/answer key, returning a score + feedback.
Why it's built this way
The hosting constraint (shared/managed WordPress hosting, no server access) shapes three specific decisions:
| Constraint | Decision |
|---|---|
| No server for a vector DB | Chunk embeddings are stored as JSON in a plain MySQL table; retrieval is brute-force cosine similarity computed in PHP. Fine up to a few thousand chunks — i.e. a normal course library. Revisit with a real vector DB (Pinecone, Qdrant, pgvector) only if the source library grows far beyond that. |
| PHP execution-time limits on shared hosting | Claude calls for hints/grading never run inline on a request. They're queued into a wp_aai_jobs table and executed on WP-Cron (aai_process_job), with the browser polling GET /job/{id} for the result. Only the (fast) chat endpoint answers synchronously. |
| Claude has no embeddings endpoint | A second, minimal API call (OpenAI text-embedding-3-small by default) is used solely for the vector step. Every actual answer, hint, and grade comes from Claude. |
Source restriction (the core requirement)
The assistant's knowledge is only what you put in AI Sources (WP Admin → AI Sources). Nothing else is ever passed to the model as fact:
- On publish, a source's content is chunked (
AAI_Chunker) and embedded (AAI_Embeddings_Client), then stored inwp_aai_source_chunks. - On a student question, the question itself is embedded and compared against stored chunks (
AAI_Vector_Store::search); only the best-matching chunks become "context." - The system prompt (
AAI_Prompts) hard-instructs Claude to answer only from that context and to explicitly say "I don't have that information" rather than guess — seechat_system_prompt(),hint_system_prompt(),grade_system_prompt(). - An AI Source can be scoped to one course or left global (available to every course).
If you never add anything under AI Sources, the assistant will consistently say it has no information — by design.
Requirements
- WordPress 5.8+, PHP 7.4+, MySQL/MariaDB (standard on virtually all hosts).
- LearnPress active.
- A Claude API key (Anthropic).
- An OpenAI API key — used only for embeddings, not for any generated text.
- Outbound HTTPS access from your host to
api.anthropic.comandapi.openai.com(allowed on virtually all shared hosts; a small minority block outbound requests by default — check with your host if calls fail).
Installation
- Zip the contents of this folder (or clone the repo) so the plugin's root files sit directly under a folder named
academy-ai-assistant. - Upload via WP Admin → Plugins → Add New → Upload Plugin, or by SFTP into
wp-content/plugins/academy-ai-assistant/. - Activate the plugin. This creates two tables:
wp_aai_source_chunksandwp_aai_jobs. - Go to Settings → Academy AI Assistant and enter your Claude API key, Claude model (defaults to
claude-sonnet-5), OpenAI API key, and embedding model. - Go to AI Sources → Add Approved Source, paste in the material the assistant is allowed to use (course notes, rubrics, answer keys), optionally scope it to a course, and publish. Re-indexing runs a couple of seconds later via WP-Cron.
Using it on the site
Two shortcodes, usable in any page/lesson template:
[aai_chat_widget]
Floating "Ask AI" chat bubble. Only visible to logged-in users. Automatically scopes retrieval to the current course when placed on a course/lesson page (via AAI_Frontend::current_course_id()).
[aai_practice_check]
A code textarea with Get a Hint and Grade My Practice buttons. Submits to the job queue and polls until a result is ready.
Wiring into LearnPress assignment/quiz submission automatically
AAI_Assignment_Hooks and AAI_Quiz_Hooks attempt best-effort integration with LearnPress hooks (learn-press/user-submit-assignment, learn-press/user-completed-quiz) so grading can fire automatically on submission instead of requiring the shortcode. These hook names/argument signatures vary across LearnPress core vs. the separate LP Assignments add-on and across versions — verify them against your actual installed plugin before relying on them (search its source under wp-content/plugins/ for the real action name, or use a logging snippet to confirm it fires). The [aai_practice_check] shortcode works regardless and is the dependable fallback.
For open-ended quiz answers specifically, LearnPress has no hook that exposes free-text answers, since it only auto-grades objective question types. Call the REST endpoint directly from your quiz results template:
fetch(AAI_Settings.restUrl + 'grade-quiz-answer', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': AAI_Settings.nonce },
body: JSON.stringify({ answer: studentAnswerText, question_id: questionId, course_id: courseId }),
}).then(r => r.json()).then(({ job_id }) => { /* poll GET /job/{job_id} */ });
REST API
All routes are under /wp-json/academy-ai/v1/ and require a logged-in user (standard X-WP-Nonce header).
| Route | Method | Body | Behavior |
|---|---|---|---|
/chat |
POST | { question, course_id? } |
Synchronous. Returns { answer, sources_used }. |
/hint |
POST | { code, course_id?, submission_id? } |
Queues a job. Returns { job_id, status: "pending" }. |
/grade-practice |
POST | { code, course_id?, submission_id? } |
Queues a job. Returns { job_id, status: "pending" }. |
/grade-quiz-answer |
POST | { answer, course_id?, question_id? } |
Queues a job. Returns { job_id, status: "pending" }. |
/job/{id} |
GET | — | Returns { status, result, error }. status is pending, processing, completed, or failed. A job's result can only be read by the user who created it (or an admin). |
/hint and /grade-practice results:
- Hint:
{ "hint": "..." } - Grade:
{ "score": 0-100, "feedback": "...", "strengths": [...], "issues": [...] }
Data model
wp_aai_source_chunks—source_id, chunk_index, content, embedding (JSON), created_at. One row per chunk of an AI Source.wp_aai_jobs—job_type, status, user_id, ref_id, input (JSON), result (JSON), error, created_at, updated_at. One row per hint/grading request.aai_source(custom post type) — the approved-content library. Post meta_aai_course_idscopes a source to a course; absent = global.
File structure
academy-ai-assistant.php Plugin bootstrap
uninstall.php Drops plugin tables/options on uninstall (keeps AI Source content)
includes/
class-aai-activator.php Creates DB tables on activation
class-aai-chunker.php Splits source text into overlapping chunks
class-aai-embeddings-client.php OpenAI embeddings API wrapper
class-aai-claude-client.php Anthropic Messages API wrapper
class-aai-vector-store.php Chunk storage + brute-force cosine-similarity search
class-aai-prompts.php All system prompts (grounding/guardrail wording lives here)
class-aai-jobs.php Async job table CRUD
class-aai-job-processor.php Runs queued jobs on WP-Cron
class-aai-source-cpt.php "AI Source" custom post type + re-indexing on save
class-aai-rest-routes.php REST API endpoints
class-aai-assignment-hooks.php Best-effort LearnPress assignment integration
class-aai-quiz-hooks.php Best-effort LearnPress quiz integration
class-aai-admin-settings.php Settings → Academy AI Assistant page
class-aai-frontend.php Shortcodes + asset enqueueing
assets/
css/chat-widget.css
js/chat-widget.js [aai_chat_widget] behavior
js/practice-check.js [aai_practice_check] behavior
Security notes
- API keys are stored as WordPress options (
aai_claude_api_key,aai_openai_api_key) — visible only to admins in Settings, not exposed to the frontend. - All REST routes require
is_user_logged_in(); job results are scoped to their creating user. - All DB access uses
$wpdb->prepare()/$wpdbparameterized methods; all output is escaped (esc_html,esc_attr,esc_url_raw). - Nonces (
wp_rest, and a dedicated one for the AI Source meta box) guard all state-changing requests.
Known gaps / next steps
- LearnPress hook names are unverified against the specific LearnPress version and any assignments add-on running on academy-tech.ir — see the callouts in
class-aai-assignment-hooks.phpandclass-aai-quiz-hooks.php. Confirm the real action names before depending on automatic grading-on-submit; the shortcode path works either way. - Course-detection meta key (
_lp_course, used inAAI_Frontend::current_course_id()and the assignment hook) is a best-effort guess — confirm against your LearnPress version's actual post meta. - At larger scale (many thousands of chunks or high concurrent chat volume), the brute-force PHP similarity search and WP-Cron job queue should be replaced with a real vector DB and a proper queue — noted here so the tradeoff is a conscious future call, not a surprise.
- No admin UI yet for browsing job history/results across all students — currently query
wp_aai_jobsdirectly if needed.
License
Proprietary — built for academy-tech.ir. Not licensed for redistribution.