ipin-vr/docs/SPEC.md

128 lines
5.3 KiB
Markdown

# SPEC — Etap 1 (PC, offline)
Specyfikacja jest **wiążącym celem buildu**. Implementacja ma być z nią zgodna.
## 1. Cel i zakres
Generować proste polskie polecenia typu *„Połóż jabłko na stole"* i opcjonalnie je
wypowiadać (Piper TTS). Typ ćwiczenia: **wykonywanie poleceń** (rozumienie ze słuchu).
W zakresie etapu 1:
- generowanie poleceń (losowo lub przez LLM),
- wypowiadanie ich (TTS, opcjonalne),
- interfejs CLI.
Poza zakresem etapu 1: STT, ocena wykonania, VR/MR, GUI.
## 2. Zasady naczelne
1. **Poprawność fleksji jest krytyczna.** Wszystkie powierzchniowe formy słów
pochodzą wyłącznie z `docs/LEXICON.md`. Nie wolno generować ani modyfikować odmian.
2. **LLM zwraca tylko identyfikatory**, nigdy polski tekst. Tekst składa kod
z leksykonu. Dzięki temu mały model nie może zepsuć gramatyki.
3. **Offline.** Brak zależności sieciowych w czasie działania poza opcjonalnym,
lokalnym serwerem LLM (localhost).
4. **Defensywność TTS.** Brak Pipera lub głosu nie przerywa działania — demo działa
wtedy w trybie tekstowym.
## 3. Stos i układ plików
Python 3.10+. Rdzeń i klient LLM korzystają wyłącznie z biblioteki standardowej
(LLM przez `urllib`, bez `requests`). Piper jest opcjonalny.
```
ipin-vr/
README.md
requirements.txt # tylko opcjonalne: piper-tts; rdzeń = stdlib
.gitignore # venv, __pycache__, *.wav, *.onnx, *.onnx.json
docs/
ipin_vr/
__init__.py
__main__.py # uruchamia cli.main()
lexicon.py # dane z docs/LEXICON.md (przepisane DOKŁADNIE)
generator.py # Clause, Command, render(), random_command()
llm.py # generator oparty na llama.cpp (opcjonalny)
tts.py # Piper przez subprocess (opcjonalny, defensywny)
cli.py # argparse + pętla główna
tests/
test_generator.py
```
Uruchamianie: `python -m ipin_vr [opcje]`.
## 4. Model danych
- `Clause(verb_id, object_id, location_id, color_id: str | None)`
- `Command(clauses: list[Clause], level: int)`
Identyfikatory muszą istnieć w leksykonie; walidacja przy tworzeniu z danych LLM.
## 5. Reguły renderowania
Klauzula = `Czasownik` + (`przymiotnik[rodzaj obiektu]` + spacja, jeśli kolor) +
`obiekt.acc` + spacja + `miejsce.fraza`.
- Czasownik w **pierwszej** klauzuli pisany wielką literą; w kolejnych — małą.
- Klauzule łączone separatorem `" i "`.
- Całość zakończona kropką.
- Poziom 1: jedna klauzula, bez koloru.
- Poziom 2: jedna klauzula, z kolorem.
- Poziom 3: dwie klauzule o **różnych** obiektach; kolor opcjonalny w każdej.
Przykłady poprawnych wyników:
- `Połóż jabłko na stole.`
- `Połóż żółtą piłkę na parapecie.`
- `Umieść niebieskie jabłko na stole.`
- `Połóż zieloną książkę na półce i umieść klucz na biurku.`
## 6. Generator losowy
`random_command(level)` losuje czasownik, obiekt, miejsce (oraz kolor zależnie od
poziomu). Dla poziomu 3 gwarantuje różne obiekty w obu klauzulach.
## 7. Generator LLM (opcjonalny)
Klient POST na endpoint zgodny z OpenAI (`/v1/chat/completions`,
domyślnie `http://localhost:8080/v1/chat/completions`).
System prompt wymusza zwrot **wyłącznie** obiektu JSON:
```json
{"clauses":[{"verb":"<id>","object":"<id>","location":"<id>","color":"<id|null>"}],"level":1}
```
Przekaż modelowi dozwolone listy identyfikatorów (z leksykonu). Po odebraniu:
1. wyciągnij JSON, sparsuj,
2. **zwaliduj** każdy identyfikator względem leksykonu,
3. renderuj z leksykonu.
Każdy błąd (timeout, brak serwera, niepoprawny JSON, nieznany identyfikator) ⇒
**fallback** na `random_command(level)`. Tekst z LLM nigdy nie trafia na wyjście.
## 8. TTS (opcjonalny, defensywny)
Piper przez `subprocess`: `piper --model <onnx> --output_file <wav>`, tekst na stdin.
Odtwarzanie zależnie od OS: `aplay` (Linux), `afplay` (macOS), `winsound` (Windows).
Brak Pipera/głosu/odtwarzacza ⇒ wypisz `[TTS off: <powód>]` i kontynuuj.
## 9. CLI
| Flaga | Domyślnie | Opis |
|---|---|---|
| `--level {1,2,3}` | 1 | poziom trudności |
| `--count N` | 5 | liczba poleceń |
| `--delay S` | 4.0 | pauza między poleceniami (s) |
| `--voice PATH` | — | ścieżka do modelu Piper `.onnx` |
| `--no-audio` | — | wyłącza TTS |
| `--llm` | — | użyj LLM do wyboru poleceń |
| `--llm-endpoint URL` | localhost:8080 | endpoint llama.cpp |
| `--llm-model NAME` | `local` | nazwa modelu |
| `--seed N` | — | deterministyczny losowy generator |
Każde polecenie wypisywane na stdout (numerowane) i — jeśli włączone audio — wypowiadane.
## 10. Testy (pytest)
`tests/test_generator.py` sprawdza `render()` dla ustalonych speców względem
oczekiwanych stringów. Pokrycie obowiązkowe:
- kolor + rzeczownik dla każdego rodzaju (m, f, n),
- poprawny miejscownik miejsca,
- łączenie dwóch klauzul (poziom 3),
- kapitalizacja: pierwsza klauzula wielką, druga małą literą.
## 11. Zależności
Rdzeń, generator i klient LLM: tylko biblioteka standardowa. `requirements.txt`
zawiera jedynie opcjonalne `piper-tts`. README opisuje pobranie głosu polskiego.
## 12. Definition of done
- `pytest` przechodzi w całości.
- `python -m ipin_vr --level 3 --count 6 --no-audio` wypisuje 6 poprawnych poleceń.
- README zawiera działający szybki start (tryb bez audio) i instrukcje audio/LLM.