EdgeVox — Claude Code 규칙
로봇을 위한 오프라인 음성 에이전트 프레임워크. 순수 Python 패키지, 클라우드 종속성 없음, CPU/CUDA/Metal에서 실행.
프로젝트 구조
edgevox/
├── edgevox/ # 소스 패키지
│ ├── audio/ # VAD, 마이크 캡처, 재생
│ ├── stt/ # STT 백엔드 (faster-whisper, sherpa-onnx)
│ ├── llm/ # llama.cpp / Gemma 통합
│ ├── tts/ # TTS 백엔드 (Kokoro, Piper, Supertonic, PyThaiTTS)
│ ├── core/ # 파이프라인 오케스트레이션
│ ├── cli/ # CLI 진입점
│ ├── ui/ # TUI 위젯
│ ├── integrations/ # ROS2 브리지 등
│ ├── tui.py # 메인 TUI 앱
│ └── setup_models.py # 모델 다운로더
│ ├── server/ # FastAPI 웹 UI + WebSocket 서버
├── webui/ # React 프론트엔드 (Vite + Tailwind)
├── scripts/ # 유틸리티 스크립트 (모델 업로드 등)
├── voices/ # 음성 설정 파일
├── docs/ # 프로젝트 문서
├── website/ # VitePress 사이트
└── pyproject.toml
진입점 (pyproject.toml 참조):
edgevox→edgevox.tui:main(기본 TUI,--web-ui는 웹,--simple-ui는 CLI)edgevox-cli→edgevox.cli.main:mainedgevox-setup→edgevox.setup_models:main
지원 언어 및 백엔드
| 언어 | STT | TTS |
|---|---|---|
| 영어, 프랑스어, 스페인어 등 | faster-whisper | Kokoro |
| 베트남어 | sherpa-onnx (zipformer) | Piper |
| 독일어, 러시아어, 아랍어, 인도네시아어 | faster-whisper | Piper |
| 한국어 | faster-whisper | Supertonic |
| 태국어 | faster-whisper | PyThaiTTS |
모델은 nrl-ai/edgevox-models (HuggingFace)에 호스팅되며, 상위 저장소로 fallback이 가능합니다.
아키텍처 원칙
- 플러그 앤 플레이, 기본적으로 사용자 정의 가능. 모든 구성 요소 — STT 백엔드, TTS 백엔드, LLM, VAD, 에이전트 루프 동작, 파이프라인 단계, 도구, 스킬, 훅 — 은 코어 코드를 수정하지 않고 교체 가능해야 합니다. 하드코딩된 경로보다는 프로토콜, 레지스트리, 데코레이터를 선호합니다. 새로운 동작은 기존 모듈에 대한 패치가 아닌 새로운 플러그인/훅/백엔드로 추가됩니다. 특정 사용 사례를 위해 코어에 조건문을 추가하는 상황이 발생하면, 한 걸음 물러서서 이를 주입 지점으로 추출하십시오.
에이전트 하네스 아키텍처
에이전트 하네스 (edgevox/agents/ + edgevox/llm/hooks_slm.py + edgevox/llm/tool_parsers/)는 docs/documentation/ 아래에 완전히 문서화되어 있습니다:
agent-loop.md— 6-파이어-포인트 루프, 병렬 디스패치, 핸드오프 쇼트서킷.hooks.md— 훅 작성 규약, 내장 훅, 순서 규칙.memory.md—MemoryStore/SessionStore/NotesFile/Compactor.interrupt.md— Barge-in 신호 + 취소 토큰 파이프라인.multiagent.md— Blackboard, BackgroundAgent, AgentPool.tool-calling.md— 파서 체인 + 문법 제약 디코딩 로드맵.
하네스 규칙
- 타입이 지정된
AgentContext필드 (ctx.tool_registry,ctx.llm,ctx.interrupt,ctx.memory,ctx.artifacts,ctx.blackboard)는 공용 API 표면입니다.ctx.state는 사용자 전용 스크래치 영역입니다 — 프레임워크 코드는 여기에 매직 키를 쓰면 안 됩니다. - 훅 소유 상태는
ctx.hook_state[id(self)]아래에 있습니다.id(self)로 키를 지정하면 동일한 훅 클래스의 두 인스턴스가 상태를 공유하지 않도록 보장합니다. - Barge-in은 권고가 아닌 강제 사항입니다. 모든
LLM.complete호출은stop_event=…를 통해ctx.interrupt.cancel_token을 전달하므로 llama-cpp의stopping_criteria가 실제로 한 디코드 단계 내에서 생성을 중단합니다. - 토크나이저 정확한 토큰 수.
estimate_tokens(messages, llm)및LLM.count_tokens는 LLM을 사용할 수 있을 때chars // 4휴리스틱을 대체합니다. CJK / 베트남어 / 태국어에 대한 올바른 컨텍스트 윈도우 결정에 필요합니다. - 도구 호출 파싱은 원시 우선입니다.
parse_tool_calls_from_content는..생각 블록을 제거하기 전에 원시 콘텐츠에 대해 감지기를 시도합니다 — Qwen3는 추론 블록 내에서 도구 호출을 내보냅니다 (llama.cpp#20837 참조). - 사전 설정 파서는 로드 시 검증됩니다.
resolve_preset(slug)는tool_call_parsers=(...)의 모든 이름이 등록된 감지기임을 단언합니다. 오타는 자동 탐지를 자동으로 비활성화하는 대신 크게 실패합니다. - 모델이 생성한 도구 호출 ID는 왕복합니다. Mistral의
[TOOL_CALLS]형식은 후속role="tool"메시지가 재사용해야 하는 9자 ID를 전달합니다.ToolCallItem.id는 이를 파서 체인과 에이전트 루프를 통해 연결합니다.
선호하는 임포트 표면
- 에이전트 프레임워크:
from edgevox.agents import LLMAgent, AgentContext, Session, Handoff, ... - 내장 훅:
from edgevox.agents.hooks_builtin import MemoryInjectionHook, TokenBudgetHook, ... - SLM 강화:
from edgevox.llm.hooks_slm import default_slm_hooks - 메모리:
from edgevox.agents.memory import JSONMemoryStore, NotesFile, Compactor, estimate_tokens - 멀티 에이전트:
from edgevox.agents.multiagent import Blackboard, BackgroundAgent, AgentPool - 인터럽트:
from edgevox.agents.interrupt import InterruptController, InterruptPolicy, EnergyBargeInWatcher
비공개 모듈이나 _agent_harness.py에 직접 접근하지 마십시오.
코딩 규칙
- Python ≥ 3.10. 최신 구문 사용 (
X | Y유니온,match,dict[str, int]). - ruff로 포맷 및 린트. 줄 길이 120.
ruff format후ruff check --fix실행. - 코드 주석에 후행 요약 금지. 무엇이 아닌 왜를 주석으로 남기세요.
- 공용 함수에는 타입 힌트. 내부 헬퍼는 명확할 때 생략 가능.
- 라이브러리 코드에 print 사용 금지. 사용자 대상 출력에는
rich/textual사용, 진단에는logging사용. - 임포트는 파일 상단에 배치. 순환 임포트, 선택적/무거운 의존성 기능 확인, CLI 시작 지연 로딩 등 구체적인 이유가 있을 때만 함수 내부로 임포트를 이동하세요. 편의성이나 "한 곳에서만 사용됨"은 이유가 아닙니다. 위로 이동하세요.
- 이유 없이 새로운 최상위 의존성 추가 금지. 표준 라이브러리를 선호하세요. 추가해야 한다면
pyproject.toml을 업데이트하세요. - 하드웨어 인식 코드 경로는 우아하게 저하되어야 함 — CUDA/Metal/CPU fallback, 가속기 누락 시 절대 충돌하지 않음.
- 모델 파일을 커밋하지 마십시오 (
.gguf,.onnx,.bin, 가중치). 이들은models/아래에 있으며 gitignore에 포함됩니다.
오디오 / 모델 규칙
- 샘플 레이트: 캡처 및 STT 입력에는 16 kHz 모노 int16.
- TTS 출력:
sounddevice를 통해 장치 레이트로 리샘플링. - VAD 프레임 크기: 32 ms (16 kHz에서 512 샘플).
- 지연 시간 예산: STT < 0.5초, LLM 첫 토큰 < 0.4초, TTS 첫 청크 < 0.1초 (RTX 3080 기준).
- 스트리밍 파이프라인을 계약으로 취급: 이벤트 루프를 차단하는 블로킹 호출을 도입하지 마십시오.
도구
- uv 패키지 관리.
uv pip install/uv venv를 기본pip/python -m venv대신 사용하세요. https://docs.astral.sh/uv/ 참조. - pre-commit은 ruff (린트 + 포맷), gitleaks, 표준 위생 훅을 실행합니다.
pre-commit install로 한 번 설치하세요. - gitleaks는 모든 커밋에서 비밀을 검사합니다. 위양성인 경우
.gitleaks.toml에 이유를 설명하는 주석과 함께 허용 목록에 추가하세요 — 발견 항목을 삭제하지 마십시오. - pytest 테스트 (
pytest, asyncio 모드 = 자동). 테스트는tests/아래에 있습니다.
워크플로 기대사항
- 파일을 편집하기 전에 읽으세요. 보지 않은 코드에 대한 변경을 제안하지 마십시오.
- 작업 완료를 선언하기 전에
ruff format+ruff check --fix를 실행하세요. - 훅을 우회하지 마십시오 (
--no-verify) — 근본 문제를 해결하세요. - 자신이나 Claude를 커밋 작성자/공동 작성자로 추가하지 마십시오. 구체적으로:
Co-Authored-By: Claude …트레일러,🤖 Generated with Claude Code푸터 금지 — 커밋 메시지는 본문 후에 끝나며, 다른 것은 없습니다. - 새 파일을 만드는 것보다 기존 파일을 편집하는 것을 선호하세요. 요청받지 않는 한 README/문서 파일을 만들지 마십시오.
- 변경 사항이 스트리밍 파이프라인에 영향을 미치는 경우, PR 설명에 수동으로 지연 시간 영향을 기록하세요.
- 마크다운 문서에서는 ASCII 아트보다 Mermaid 다이어그램을 선호하세요 (
docs/,website/,README.md, PR 설명). GitHub와 VitePress는mermaid펜스 블록을 기본적으로 렌더링합니다. 손으로 그린 박스-라인 ASCII는 읽기 어렵고, 깔끔하게 편집이 불가능하며, 고정폭 글꼴 변경 시 깨집니다. 허용되는 유일한 ASCII 다이어그램은 디렉토리 트리 (├──/└──)입니다 — 이는 그대로 유지됩니다.
문서 작성 (docs/, README.md, website/)
(,),@,:,,,&,<br/>, 또는 문자, 숫자,_,-이외의 구두점을 포함하는 Mermaid 레이블은 항상 따옴표로 묶으세요. 노드에는["foo(args)"]를, 엣지 레이블에는-->|"@tool call"|를 사용하세요.[...]또는|...|내부에 따옴표 없는 괄호는 플로우차트 파서를 조용히 깨뜨립니다 — 페이지는 여전히 로드되지만 다이어그램이 사라집니다. Mermaid 블록을 편집한 후, 로컬에서 페이지를 열고 (npm run docs:dev) 브라우저 콘솔에Parse error on line N항목이 없는지 확인하세요. 이러한 깨짐은 소스 리뷰에서는 보이지 않습니다.- 작업 완료를 선언하기 전에 개발 서버에서 문서를 테스트하세요.
npm run docs:dev(VitePress,:5173)은 마크다운 소스가 숨기는 Mermaid 파싱 오류, 깨진 링크, 죽은 앵커를 잡아냅니다. 시각적 변경(새 다이어그램, 레이아웃, 히어로, 사이드바)의 경우, 리뷰어가 서버를 시작하지 않고 회귀를 발견할 수 있도록 영향을 받는 페이지의 Playwright 스크린샷을 캡처하세요. - 페이지 간 링크는 루트 절대 경로이며, 확장자가 없습니다 —
/documentation/hooks,./hooks.md또는hooks가 아님 (후자는cleanUrls: true에서 잘못 해석됨). Mermaid 노드 클릭 대상도 동일한 규칙을 따릅니다. - 사이드바 등록은 필수입니다.
docs/documentation/아래의 새 페이지는docs/.vitepress/config.ts의sidebar블록에 추가될 때까지 보이지 않습니다. 둘을 동시에 업데이트하세요.
하지 말아야 할 것
- 클라우드 API 호출이나 텔레메트리를 추가하지 마세요. EdgeVox는 오프라인 우선입니다.
- GPL 라이선스 의존성을 도입하지 마세요 (프로젝트는 MIT).
- 새 의존성을 추가하기 전에 라이선스 확인이 필수입니다. 패키지의 실제 라이선스(PyPI/GitHub/문서 — 단순 추측이 아님)를 확인하고 결과를
pyproject.toml의 dep 옆에 인라인으로 기록하세요 (예:# MIT/# LGPL-3 — dynamic-linked, compatible). 거부해야 할 카피레프트 라이선스: GPL-2/3, AGPL, SSPL. LGPL은 순수 동적 링크 라이브러리(PySide6, Qt Multimedia, rlottie)에만 허용됩니다. CC-BY-SA는 에셋(SVG 조각 세트 등)에 허용되며, 소스 수준 의존성에는 절대 허용되지 않습니다. 의심스러우면 허용적인 대안을 선택하거나 논의를 위해 플래그를 지정하세요. dist/,build/,*.egg-info/, 모델 가중치 또는 녹음을 커밋하지 마세요.- 작업이 요구하는 것 이상으로 추측성 추상화나 "미래 보장"을 추가하지 마세요.