# 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":"","object":"","location":"","color":""}],"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 --output_file `, tekst na stdin. Odtwarzanie zależnie od OS: `aplay` (Linux), `afplay` (macOS), `winsound` (Windows). Brak Pipera/głosu/odtwarzacza ⇒ wypisz `[TTS off: ]` 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.