Sikia interviewer v6c (LoRA adapter, card format 12n)
A LoRA adapter on stock Gemma 4 E2B that reads a respondent's reply during an offline survey interview and
returns one short JSON reply. It follows the Sikia offline interview design: the app leads the interview
(question order, skip logic, repeat rounds, follow-ups, everything that is spoken) and the model only
listens. Each call gets the standing instructions (instructions.txt) and one short card.
Versions. v6c is trained like v4 (same base model, card format, instructions and settings) on corrected and wider data. It follows v6b, adding training rows for open comment questions and for numbers. v4 remains the version in use until v6c passes Sikia's relevance test.
Status: research pilot. Best of our versions on real speech and on numbers in our tests; Sikia's relevance test (off-topic replies in realistic interviews) is pending. Don't use it for real data collection yet.
What it does
| Card | Returns |
|---|---|
| Question (any form question: single or multiple choice, whole number, decimal, date, text) | {"act", "value", "say"} — act is answer, clarify, skip_request, impatient, correction (an earlier answer) or off_topic; say is always empty |
CURRENT: yes/no (another round? skip this question?) |
{"act": "yes"/"no"/"clarify", "say": ""} |
TASK: list items (split a spoken list) |
{"items": [...]} in the respondent's words |
TASK: probe (follow-up check on a researcher's goal) |
{"new_items": [...], "goal_met": bool, "follow_up": ""} |
goal_met is true only when the goal states a target ("Target: at least 3 points.") and that many points
have been heard; otherwise it is false and the app decides when to stop.
Example
LANG: sw
CURRENT: q=top_constraints type=select_multiple "Changamoto kubwa ni zipi?"
OPTIONS: 1=Ukame 2=Wadudu 3=Magonjwa 4=Udongo duni 5=Soko 6=Mbegu duni 7=Hakuna
CONTEXT: -
RESPONDENT SAID: "Wadudu ni wengi, na pia soko halina bei nzuri."
{"act": "answer", "value": "2 5", "say": ""}
Cards must match the training format byte for byte. slmlib/cards.py in the project code writes every card
and applies the app's post-processing (option numbers back to codes, the respondent's own words for text,
the year of a date).
Try it
Accept the Gemma licence on Hugging Face first, and use transformers 5.17 or later (earlier 5.x versions compute Gemma 4 E2B differently with the cache on, and the adapter then has no effect).
import torch
from huggingface_hub import hf_hub_download
from peft import PeftModel
from transformers import AutoModelForImageTextToText, AutoProcessor
BASE, ADAPTER = "google/gemma-4-E2B-it", "smutuvi/sikia-listener-v6c-gemma4-e2b-lora"
proc = AutoProcessor.from_pretrained(BASE)
model = AutoModelForImageTextToText.from_pretrained(BASE, dtype=torch.bfloat16, device_map="auto")
model = PeftModel.from_pretrained(model, ADAPTER).eval()
system = open(hf_hub_download(ADAPTER, "instructions.txt"), encoding="utf-8").read().strip()
card = """LANG: sw
CURRENT: q=household_size type=integer "Watu wangapi wanaishi kwenye kaya yako?"
RANGE: 1 to 30
CONTEXT: -
RESPONDENT SAID: "Tuko saba, mimi na mke wangu na watoto watano." """
msgs = [{"role": "system", "content": system}, {"role": "user", "content": card.strip()}]
prompt = proc.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True, enable_thinking=False)
enc = proc.tokenizer(prompt, return_tensors="pt", add_special_tokens=False).to(model.device)
out = model.generate(**enc, max_new_tokens=80, do_sample=False)
print(proc.tokenizer.decode(out[0, enc["input_ids"].shape[1]:], skip_special_tokens=True))
# {"act": "answer", "value": "7", "say": ""}
With the project code, a whole interview on any form, typed or spoken:
python demo/form_interview.py --adapter smutuvi/sikia-listener-v6c-gemma4-e2b-lora --form bean_tricot # terminal
python demo/form_interview.py --adapter smutuvi/sikia-listener-v6c-gemma4-e2b-lora --form clinic --lang en
python demo/form_app.py # browser; lists this adapter
Speech is transcribed separately (the base model with the adapter switched off, or the ndizi speech model): one skill per adapter.
Training data
8,922 cards; the data itself is not published (it contains what respondents said).
| Source | Cards | |
|---|---|---|
| Real bean-trial replies (Swahili), question and follow-up cards | 2,095 | labels hand-checked on the test set, corrected in training |
| Drafted interviews on 7 topics (Swahili, English) | 3,876 | free text follows the core-answer rule; open opinion questions included |
| Sikia practice interviews on 14 real forms (Swahili, English) | 2,111 | all reply types, weak-spot sets, and off-topic replies to open questions |
| Real Sikia replies on other forms (Swahili) | 138 | dev interviews only, usable labels only |
| Written rows for two weak spots (English and Swahili) | 702 | open comment questions; numbers |
Changes from v4's data:
- Follow-up replies are follow-up cards only. 203 replies to a follow-up question had been exported as first-question cards, with "nothing new" keyed as the form's "Hakuna".
- Bean labels follow the question's direction. A "like" card codes what the respondent likes, a "dislike" card what they dislike; "nothing I dislike, it grew well" is Hakuna.
- Free text keeps the core answer: one unbroken stretch of the reply, copied exactly, without fillers or lead-ins (agreed with Sikia, 5 October 2026).
- Off-topic replies to open questions (Sikia): answers to another question of the same form, and everyday talk, with as many real and roundabout answers.
- Open comment questions ("Any additional comments or suggestions?"), mostly English: a complaint, request, suggestion, conditional or mixed opinion on the subject is the answer.
- Numbers: money said in words against ranges of different sizes ("shilingi mia tano" is 500, whatever the range), numbers with a zero inside (808, 2707), zero said in words ("bure"), fractions ("robo tatu" is 0.75), and one-word counts ("Sita").
Nothing comes from Sikia's 4 held-out forms or from the forms in Sikia's relevance test.
Evaluation
All versions on the same test cards. v6c is shown for two training runs; this repo holds seed 42.
| v4 | v6b | v6c (seed 42) | v6c (seed 1) | |
|---|---|---|---|---|
| Real Sikia replies on other forms (118): right answers | 101 | 103 | 104 | 102 |
| Real Sikia: answers misread as refusal, correction or off-topic | 1 | 2 | 1 | 2 |
| Real Sikia: decimals (2) | 1 | 1 | 2 | 2 |
| Drafted forms never used in training: whole numbers (117) | 116 | 109 | 117 | 117 |
| Drafted forms never used in training: multi-choice F1 | 0.959 | 0.966 | 0.981 | 0.954 |
| Real bean speech: trait codes, F1 | 0.899 | 0.907 | 0.902 | 0.879 |
| Real bean speech: "nothing" recognised | 0.968 | 1.000 | 0.968 | 1.000 |
| Real bean speech: follow-up points, word overlap | 0.702 | 0.767 | 0.722 | 0.727 |
| Real bean speech: goal met, goal with a target | 0.838 | 0.838 | 0.829 | 0.810 |
Real bean speech: 189 hand-checked question cards and 198 hand-reviewed follow-up checks. Real Sikia: Sikia's fair
answer-key benchmark from interviews marked do-not-train. Full scores by group and language: scores_v12.json.
Differences of 1 to 3 cards are within run-to-run noise.
Known weak spots: the follow-up check gained less than in v6b (points heard 0.72 against 0.77, goal met with a target slightly below v4); free text on real Sikia speech (5 of 16 exact; the benchmark keys often summarise); off-topic replies that answer a different question (Sikia's relevance test pending).
Training procedure
LoRA (PEFT) on the language model's linear layers only; the vision and audio towers are untouched. Loss on
the JSON reply only. Rank 16, alpha 32, 3 epochs, effective batch 16 (2 × 8), bf16, one GPU, transformers 5.17,
seed 42. Settings in train_config.json.