128 lines
5.3 KiB
Markdown
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.
|