Verb Validator
Overview
Verb validation enriches verb lemmas with LLM-generated data. It performs four tasks, each with its own flag on verb_lemmas so completed work is never repeated:
- Common classification (
common_validated) — decides whether a verb should be marked “common” for learners - Verbals (
verbals_validated) — generates missing participle forms (present and past participles, plus verbal nouns for Turkish) - Translations (
translations_validated) — creates or updates translations across all supported languages - Conjugations (
conjugations_validated) — generates conjugated forms for all required tenses and moods
Each task is a RubyLLM prompt service following the standard architecture (see Prompt Architecture): a BasePrompt subclass with a YAML prompt file, a Schematist::Schema, and a value object, wrapped by a service that persists the result.
Entry points
There are three ways validation runs; all go through the same services.
1. Lazy validation on the public verb page
Resources::VerbsController#show enqueues a job for each unvalidated dimension when a verb page is viewed:
Resources::Verbs::ValidateCommonJob.perform_later(@verb_lemma.id) unless @verb_lemma.common_validated
Resources::Verbs::ValidateTranslationsJob.perform_later(@verb_lemma.id) unless @verb_lemma.translations_validated
Resources::Verbs::ValidateVerbalsJob.perform_later(@verb_lemma.id) unless @verb_lemma.verbals_validated
(The conjugations job is currently not enqueued from the controller.)
2. Admin “Validate Verb” action
The admin VerbLemmas page (app/admin/verbs/verb_lemmas.rb) has a validate member action that runs all four services synchronously, updates the per-dimension flags, and shows a diff-based flash (common status change, verbals/translations created).
3. Verb import rake task
lib/tasks/verbs_import.rake enqueues all four validation jobs for each newly created verb.
Components
Jobs (app/jobs/resources/verbs/, queue: llm)
Resources::Verbs::ValidateCommonJobResources::Verbs::ValidateTranslationsJobResources::Verbs::ValidateVerbalsJobResources::Verbs::ValidateConjugationsJob
Each job loads the verb lemma, calls its service, and sets the corresponding *_validated flag on success. On failure it logs and re-raises so Sidekiq retries.
Services (app/services/resources/verbs/)
CommonClassifierService
- Calls
CommonClassifierPromptto evaluate if the verb is common - Updates
verb_lemma.commonif the recommendation differs from the current value - Returns
{status: :success}or{status: :error, message: "..."}
VerbalsGeneratorService
- Skips if verbals already exist
- Calls
VerbalsGeneratorPromptto generate participle forms - Creates
Verbalrecords for each required form
TranslationsGeneratorService
- Iterates through target languages (excluding the source language)
- Calls
TranslationsGeneratorPromptfor each target language - Creates or updates
VerbLemmaTranslationrecords
ConjugationsGeneratorService
- Skips if conjugations already exist
- Fetches required tenses from the
tensestable for the language - Calls
ConjugationsGeneratorPromptto generate all conjugated forms - Creates
Conjugationrecords and enqueuesPrepareAudioJobwhen conjugations were created
Prompts, schemas, values
Each service pairs with a prompt class in app/prompts/resources/verbs/, a YAML prompt in config/prompts/resources/verbs/, a schema in app/schemas/resources/verbs/, and a value object in app/values/resources/verbs/. In development the prompts return FakeAI payloads unless LIVE_MODE=true (see FakeAI).
Data models
VerbLemma
common: whether the verb is common for learnerscommon_validated/verbals_validated/translations_validated/conjugations_validated: per-dimension completion flags
Verbal: participle forms
type_key: “present_participle”, “past_participle”, or “verbal_noun”form,gender(m/f/n),number(sg/pl)
VerbLemmaTranslation
language: target language codelemma: translation text (infinitive form)
Conjugation
tense_id,person_seq(1-6),person_label(e.g. “je”, “tu”),full_formregular: whether the form follows regular patternsnumber(“sg”/”pl”) andnegativeflags where applicable (e.g. Turkish)
Task details
Common classification
Criteria for “common” verbs:
- Suitable for A1-B1 learners
- Frequently used in everyday conversation
- Appears in standard textbooks and learning materials
- Includes essential irregular verbs (être, avoir, aller, faire, etc.)
- Excludes technical, literary, archaic, specialized, rare, or regional verbs
Output: common (true/false), confidence (0.0-1.0), reasons (array of strings). The verb lemma is updated if the recommendation differs from the current status.
Verbals
| Language | Forms Generated |
|---|---|
| French (fr) | present_participle, past_participle (m/f sg/pl) |
| Spanish (es) | present_participle, past_participle (m/f sg/pl) |
| Italian (it) | present_participle, past_participle (m/f sg/pl) |
| Portuguese (pt) | present_participle, past_participle (m/f sg/pl) |
| Catalan (ca) | present_participle, past_participle (m/f sg/pl) |
| German (de) | present_participle, past_participle |
| English (en) | present_participle, past_participle |
| Turkish (tr) | present_participle, past_participle, verbal_noun |
Translations
Target languages: de, en, es, fr, it, pt, ca, tr. One LLM call per target language; multiple meanings are comma-separated. English uses the base form without “to” (e.g. “go”); all other languages use the infinitive.
Conjugations
Required tenses come from the tenses table for the verb’s language. The LLM generates all persons (1-6) for each tense; language-specific features (e.g. Turkish negative forms) are handled per conjugation. An audio job is enqueued for newly created conjugations.
Error handling
- Each service returns
{status: :error, message: "..."}on failure; jobs log and re-raise so Sidekiq retries. - The admin action runs all four services even if one fails, and reports every error in the flash.
- A dimension’s
*_validatedflag is only set when its service succeeds, so failed work is retried on the next entry-point trigger.
Typical error messages: “VerbLemma must be persisted”, “VerbLemma must have a language”, “Missing default infinitive”, “No tenses found for language”, “LLM returned an error: …”.
Performance
Each fully unvalidated verb costs up to 10 LLM calls: 1 (common) + 1 (verbals) + 7 (translations, one per target language) + 1 (conjugations). The lazy controller path spreads this over real page views; jobs run on the llm Sidekiq queue.
Related Documentation
- Verb Architecture - Overall verb system design
- Prompt Architecture - LLM prompt system design
- Sentence Matching - How verbs are matched to sentences