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:

  1. Common classification (common_validated) — decides whether a verb should be marked “common” for learners
  2. Verbals (verbals_validated) — generates missing participle forms (present and past participles, plus verbal nouns for Turkish)
  3. Translations (translations_validated) — creates or updates translations across all supported languages
  4. 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::ValidateCommonJob
  • Resources::Verbs::ValidateTranslationsJob
  • Resources::Verbs::ValidateVerbalsJob
  • Resources::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 CommonClassifierPrompt to evaluate if the verb is common
  • Updates verb_lemma.common if the recommendation differs from the current value
  • Returns {status: :success} or {status: :error, message: "..."}

VerbalsGeneratorService

  • Skips if verbals already exist
  • Calls VerbalsGeneratorPrompt to generate participle forms
  • Creates Verbal records for each required form

TranslationsGeneratorService

  • Iterates through target languages (excluding the source language)
  • Calls TranslationsGeneratorPrompt for each target language
  • Creates or updates VerbLemmaTranslation records

ConjugationsGeneratorService

  • Skips if conjugations already exist
  • Fetches required tenses from the tenses table for the language
  • Calls ConjugationsGeneratorPrompt to generate all conjugated forms
  • Creates Conjugation records and enqueues PrepareAudioJob when 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 learners
  • common_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 code
  • lemma: translation text (infinitive form)

Conjugation

  • tense_id, person_seq (1-6), person_label (e.g. “je”, “tu”), full_form
  • regular: whether the form follows regular patterns
  • number (“sg”/”pl”) and negative flags 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 *_validated flag 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.


This site uses Just the Docs, a documentation theme for Jekyll.