WP Manifestindependent plugin directory
manifest / ai / academy-ai-assistant

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

0stars
0forks

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.zip

Readme

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:

  1. On publish, a source's content is chunked (AAI_Chunker) and embedded (AAI_Embeddings_Client), then stored in wp_aai_source_chunks.
  2. 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."
  3. 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 — see chat_system_prompt(), hint_system_prompt(), grade_system_prompt().
  4. 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.com and api.openai.com (allowed on virtually all shared hosts; a small minority block outbound requests by default — check with your host if calls fail).

Installation

  1. 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.
  2. Upload via WP Admin → Plugins → Add New → Upload Plugin, or by SFTP into wp-content/plugins/academy-ai-assistant/.
  3. Activate the plugin. This creates two tables: wp_aai_source_chunks and wp_aai_jobs.
  4. 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.
  5. 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_chunkssource_id, chunk_index, content, embedding (JSON), created_at. One row per chunk of an AI Source.
  • wp_aai_jobsjob_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_id scopes 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()/$wpdb parameterized 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.php and class-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 in AAI_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_jobs directly if needed.

License

Proprietary — built for academy-tech.ir. Not licensed for redistribution.

Read the full README on GitHub →