Guidance for AI coding agents working in this repository.
A gRPC CLI client and Python library for the Techmo ASR (Automatic Speech Recognition) Service. Entry point: asr_client.__main__:main (installed as asr-client). The gRPC stubs come from techmo-asr-api (fetched from github.com/techmo-pl/asr-api-python at install time) and are generated at install time — they are never committed.
Run once after cloning:
./install.sh # create .venv and install package + test depsinstall.sh requires system packages to build pyaudio — it will tell you exactly what's missing:
apt install python3-dev portaudio19-dev build-essentialIf from asr_api import ... fails, re-run ./install.sh (it fetches techmo-asr-api from GitHub automatically).
# Fast — no live service required
pytest tests/ -k "not integration"
# With coverage
pytest tests/ --cov=asr_client --cov-report=term-missing -k "not integration"
# Full matrix (Python 3.8–3.14), requires tox-uv
tox
# Single Python version
tox -e py311
# Integration tests — requires a live ASR service
pytest tests/test_integration.py --asr-service-address HOST:PORTAll tests except integration are self-contained. Integration tests auto-skip when --asr-service-address is absent.
asr_client/
├── __init__.py # create_grpc_channel(), create_grpc_channel_credentials(),
│ # _generate_request_with_traceback decorator
├── __main__.py # CLI: argument parsing, TLS setup, streaming dispatch
├── audio_processing.py # AudioFile, AudioFileStream, MicrophoneStream
├── v1.py # v1 and v1p1 API (shared implementation, stub selected by arg)
├── dictation.py # Legacy dictation API
└── VERSION.py # __version__ string
The asr_api package (gRPC stubs) is installed as techmo-asr-api @ git+https://github.com/techmo-pl/asr-api-python.git@1.1.4 — fetched from GitHub at install time, not from PyPI.
Never commit generated files. *_pb2.py and *_pb2_grpc.py are produced by grpc_tools.protoc at install time. They live in the submodule's build output, not in this repo.
Do not add modules to ignore_errors in pyproject.toml. All existing source modules have mypy errors suppressed (ignore_errors = true) as a legacy exception. New code must be written with full mypy --strict compliance.
Do not remove the warnings.catch_warnings() block in audio_processing.py. It suppresses a SyntaxWarning from pydub on Python 3.14. Removing it breaks the import on that version.
Do not change gRPC metadata key names without verifying server-side expectations:
v1/v1p1API: session metadata key is"session-id"(hyphen)dictationAPI: session metadata key is"session_id"(underscore)
grpc.RpcError exits 0. main() catches it, prints it, and returns normally. Do not write tests that assert returncode != 0 to detect recognition failures — check stdout/stderr content instead.
| Rule | Value |
|---|---|
| Line length | 160 (not 88 or 79) |
| Linter | ruff — rules E,W,F,I,S,UP,B; ignores S101, S603, S607 |
| Type checking | mypy --strict |
| Shell scripts | shellcheck + shfmt (4-space indent) |
160 is the enforced line length. Standard PEP 8 limits do not apply here.
gRPC's C runtime silently swallows exceptions raised inside generator functions passed to a stub. Without this decorator the failure surfaces only as StatusCode.UNKNOWN "Exception iterating requests!" with no traceback. Apply it to every generator that feeds a stub:
from asr_client import _generate_request_with_traceback
from itertools import chain
@_generate_request_with_traceback
def _generate_config_request(...): yield ...
@_generate_request_with_traceback
def _generate_data_requests(audio_stream, ...): yield ...
responses = stub.StreamingRecognize(
chain(_generate_config_request(...), _generate_data_requests(...)),
metadata=...,
)--api-version |
Module | Stub | Response key |
|---|---|---|---|
v1p1 (default) |
v1.py |
asr_api.v1p1.AsrStub |
"result" |
v1 |
v1.py |
asr_api.v1.AsrStub |
"result" |
dictation |
dictation.py |
asr_api.dictation.SpeechStub |
"results" |
v1 and v1p1 share one implementation file; the stub is selected by the api_patch_version argument (None → v1, 1 → v1p1).
build_additional_config_specs_dict() transforms kwarg names to server keys:
__→.(double underscore → dot)_→-(single underscore → hyphen)
New tuning CLI args must follow this convention: --decoder.new-param with dest="decoder__new_param". Args with value "NA" are excluded. --max-hypotheses-for-softmax is always forwarded regardless.
Defined as local functions inside parse_args(): assure_int, positive_int, unsigned_int, non_empty_str. The Once action prevents an argument from being repeated. Use these when adding new CLI arguments.
The default max incoming message size is 4 MB. Sending a file larger than ~4 MB as a single chunk fails with StatusCode.RESOURCE_EXHAUSTED. Always use --audio-stream-chunk-duration for large files in tests and examples.
- Add inside
parse_args()in__main__.pyusing an existing validator or a new one following the same pattern. - Thread the value through to the request builder or config dict.
- Add a test in
test_cli.py.
- Add
v2.pyfollowing the pattern inv1.py. - Add the version string to
--api-versionchoices. - Add
elif args.api_version == "v2":in the dispatch inmain()beforeelse: raise AssertionError. - Add integration tests in
test_integration.py.
- Edit
asr_client/VERSION.py. - Update
CHANGELOG.md. - Do not edit
pyproject.toml— it reads the version dynamically fromVERSION.py.
| File | What it tests |
|---|---|
test_cli.py |
Argument parsing, help text |
test_audio.py |
WAV loading, streaming (wav_path fixture generates silence) |
test_channel.py |
gRPC channel creation |
test_version.py |
Version string format |
test_integration.py |
Live ASR service (auto-skips without --asr-service-address) |
conftest.py |
Registers --asr-service-address; provides wav_path fixture |
asr_service_address and audio_wav fixtures are defined in test_integration.py, not conftest.py. audio_wav requires data/audio.wav and auto-skips if absent.