liveavatar-platform-rtc 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- liveavatar_platform_rtc-1.0.0/.codegraph/.gitignore +16 -0
- liveavatar_platform_rtc-1.0.0/.codegraph/codegraph.db +0 -0
- liveavatar_platform_rtc-1.0.0/.codegraph/codegraph.db-shm +0 -0
- liveavatar_platform_rtc-1.0.0/.codegraph/codegraph.db-wal +0 -0
- liveavatar_platform_rtc-1.0.0/.gitattributes +2 -0
- liveavatar_platform_rtc-1.0.0/.gitignore +198 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/.gitignore +1 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/final-fix-report.md +66 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/progress.md +6 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/task-1-brief.md +102 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/task-1-report.md +104 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/task-2-brief.md +63 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/task-2-report.md +86 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/task-3-brief.md +59 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/task-3-report.md +100 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/task-4-brief.md +89 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/task-4-report.md +90 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/task-5-brief.md +60 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/task-5-report.md +71 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/task-6-brief.md +56 -0
- liveavatar_platform_rtc-1.0.0/.superpowers/sdd/task-6-report.md +98 -0
- liveavatar_platform_rtc-1.0.0/LICENSE +201 -0
- liveavatar_platform_rtc-1.0.0/MANIFEST.in +4 -0
- liveavatar_platform_rtc-1.0.0/PKG-INFO +170 -0
- liveavatar_platform_rtc-1.0.0/PROTOCOL.md +147 -0
- liveavatar_platform_rtc-1.0.0/PROTOCOL.zh.md +135 -0
- liveavatar_platform_rtc-1.0.0/README.md +138 -0
- liveavatar_platform_rtc-1.0.0/README.zh.md +129 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/plans/2026-07-15-rtc-agent-sdk-v2.md +1027 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/plans/2026-07-15-rtc-chat-default-port-8009.md +89 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/plans/2026-07-15-rtc-chat-demo.md +470 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/plans/2026-07-15-rtc-chat-tts-connect-cancellation.md +167 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/plans/2026-07-15-rtc-chat-tts-null-cancel.md +92 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/plans/2026-07-15-rtc-chat-unexpected-disconnect-diagnostics.md +176 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/plans/2026-07-15-rtc-data-channel-without-topic.md +185 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/plans/2026-07-16-rtc-chat-fork-join-startup.md +388 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/plans/2026-07-16-rtc-chat-text-input.md +149 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/specs/2026-07-15-rtc-agent-sdk-v2-design.md +406 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/specs/2026-07-15-rtc-chat-default-port-8009-design.md +21 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/specs/2026-07-15-rtc-chat-demo-design.md +188 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/specs/2026-07-15-rtc-chat-tts-connect-cancellation-design.md +46 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/specs/2026-07-15-rtc-chat-tts-null-cancel-design.md +17 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/specs/2026-07-15-rtc-chat-unexpected-disconnect-diagnostics-design.md +48 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/specs/2026-07-15-rtc-data-channel-without-topic-design.md +54 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/specs/2026-07-16-rtc-chat-fork-join-startup-design.md +99 -0
- liveavatar_platform_rtc-1.0.0/docs/superpowers/specs/2026-07-16-rtc-chat-text-input-design.md +58 -0
- liveavatar_platform_rtc-1.0.0/examples/basic_agent.py +80 -0
- liveavatar_platform_rtc-1.0.0/pyproject.toml +60 -0
- liveavatar_platform_rtc-1.0.0/src/liveavatar_rtc/__init__.py +73 -0
- liveavatar_platform_rtc-1.0.0/src/liveavatar_rtc/api_client.py +75 -0
- liveavatar_platform_rtc-1.0.0/src/liveavatar_rtc/client.py +194 -0
- liveavatar_platform_rtc-1.0.0/src/liveavatar_rtc/errors.py +37 -0
- liveavatar_platform_rtc-1.0.0/src/liveavatar_rtc/event_dispatcher.py +45 -0
- liveavatar_platform_rtc-1.0.0/src/liveavatar_rtc/livekit_manager.py +452 -0
- liveavatar_platform_rtc-1.0.0/src/liveavatar_rtc/py.typed +0 -0
- liveavatar_platform_rtc-1.0.0/src/liveavatar_rtc/session.py +317 -0
- liveavatar_platform_rtc-1.0.0/src/liveavatar_rtc/types.py +87 -0
- liveavatar_platform_rtc-1.0.0/tests/test_api_client.py +255 -0
- liveavatar_platform_rtc-1.0.0/tests/test_client.py +344 -0
- liveavatar_platform_rtc-1.0.0/tests/test_event_dispatcher.py +209 -0
- liveavatar_platform_rtc-1.0.0/tests/test_livekit_manager.py +1008 -0
- liveavatar_platform_rtc-1.0.0/tests/test_session.py +589 -0
- liveavatar_platform_rtc-1.0.0/tests/test_types.py +41 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
|
|
110
|
+
# pdm
|
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
112
|
+
#pdm.lock
|
|
113
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
114
|
+
# in version control.
|
|
115
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
116
|
+
.pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
121
|
+
__pypackages__/
|
|
122
|
+
|
|
123
|
+
# Celery stuff
|
|
124
|
+
celerybeat-schedule
|
|
125
|
+
celerybeat.pid
|
|
126
|
+
|
|
127
|
+
# SageMath parsed files
|
|
128
|
+
*.sage.py
|
|
129
|
+
|
|
130
|
+
# Environments
|
|
131
|
+
.env
|
|
132
|
+
.venv
|
|
133
|
+
env/
|
|
134
|
+
venv/
|
|
135
|
+
ENV/
|
|
136
|
+
env.bak/
|
|
137
|
+
venv.bak/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# Pyre type checker
|
|
155
|
+
.pyre/
|
|
156
|
+
|
|
157
|
+
# pytype static type analyzer
|
|
158
|
+
.pytype/
|
|
159
|
+
|
|
160
|
+
# Cython debug symbols
|
|
161
|
+
cython_debug/
|
|
162
|
+
|
|
163
|
+
# PyCharm
|
|
164
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
165
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
166
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
167
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
168
|
+
#.idea/
|
|
169
|
+
|
|
170
|
+
# Ruff stuff:
|
|
171
|
+
.ruff_cache/
|
|
172
|
+
|
|
173
|
+
# PyPI configuration file
|
|
174
|
+
.pypirc
|
|
175
|
+
|
|
176
|
+
# Cursor
|
|
177
|
+
# Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to
|
|
178
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
179
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
180
|
+
.cursorignore
|
|
181
|
+
.cursorindexingignore
|
|
182
|
+
|
|
183
|
+
# macOS
|
|
184
|
+
.DS_Store
|
|
185
|
+
|
|
186
|
+
# Claude Code local settings
|
|
187
|
+
.claude/
|
|
188
|
+
|
|
189
|
+
# oh-my-claudecode runtime state
|
|
190
|
+
.omc/
|
|
191
|
+
|
|
192
|
+
# Local isolated development worktrees
|
|
193
|
+
.worktrees/
|
|
194
|
+
|
|
195
|
+
# Accidental nested project copy (tracked by mistake)
|
|
196
|
+
/liveavatar-platform-rtc-python/
|
|
197
|
+
ragflow/
|
|
198
|
+
.codegraph/daemon.pid
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# RTC chat final fix report
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Implemented in `/tmp/rtc-chat-final-fix` with surgical changes to `agent.py`, `llm.py`, `tts.py`, `QUICKSTART.md`, and focused tests.
|
|
6
|
+
|
|
7
|
+
- Chat readiness now has explicit state/event gating. Handlers register early; audio is dropped before all waits and ASR connect complete; text waits for readiness and is released without hanging by close/failure. Ready is set only after successful ASR connect.
|
|
8
|
+
- TTS terminal items retain per-generation provider failures. JSON `error` and abnormal close raise from `frames()` with provider detail; normal done/close completes normally.
|
|
9
|
+
- Response failures send the request-context error event and do not append conversation or log completion.
|
|
10
|
+
- LLM turns use candidate history and commit only after a successful stream/callback; failure and cancellation preserve the pre-turn history. `close()` supports sync/async client close and is idempotent.
|
|
11
|
+
- Pipeline close includes LLM close in best-effort parallel cleanup.
|
|
12
|
+
- Runtime readiness failure schedules active cleanup, guarded by readiness-task/pipeline/client identity; the done callback never directly awaits or cancels itself.
|
|
13
|
+
- RTC sample-rate publish/format failures propagate `avatar configured for 24 kHz output / align avatar configuration`; QUICKSTART uses the same wording.
|
|
14
|
+
- Provider construction failure before pipeline ownership best-effort closes every already-created ASR/LLM/TTS provider before disconnecting the RTC client.
|
|
15
|
+
- Invalid Base64 or malformed TTS PCM is retained as a generation terminal error (`invalid TTS PCM payload from provider`), raised by `frames()`, and cannot produce a completed response.
|
|
16
|
+
- Added race/failure tests for readiness gating and close release, stale readiness cleanup, provider errors, sample-rate guidance, transactional LLM failures, and client close.
|
|
17
|
+
|
|
18
|
+
## Verification command summaries
|
|
19
|
+
|
|
20
|
+
TDD red run:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
PYTHONPATH=... python -m pytest -q tests/test_llm.py tests/test_tts.py tests/test_agent.py
|
|
24
|
+
9 failed, 50 passed in 0.93s
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Focused green run after implementation:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
PYTHONPATH=... python -m pytest -q tests/test_agent.py tests/test_llm.py tests/test_tts.py
|
|
31
|
+
60 passed, 1 failed (the remaining failure exposed a bare re-raise of the original AudioPublishError)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Residual-review red/green run:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
python -m pytest -q tests/test_agent.py::test_tts_factory_failure_closes_created_llm_and_other_resources tests/test_tts.py::test_invalid_audio_payload_raises_in_frames
|
|
38
|
+
RED: 2 failed in 0.61s
|
|
39
|
+
GREEN: 2 passed in 0.50s
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Final full offline suite after the two residual review fixes (combined venv/site-packages; localhost bind allowed for aiohttp route tests):
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
PYTHONPATH=/tmp/rtc-chat-final-fix:/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/.venv/lib/python3.14/site-packages:/Users/luffer/workspace/liveavatar-platform-rtc-python/src /Users/luffer/workspace/liveavatar-platform-rtc-python/.venv/bin/python -m pytest -q
|
|
46
|
+
........................................................................ [ 92%]
|
|
47
|
+
...... [100%]
|
|
48
|
+
78 passed in 0.84s
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Compile and independence/forbidden-symbol scan:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
PYTHONPATH=... python -m compileall -q agent.py asr.py llm.py tts.py tests
|
|
55
|
+
! rg -n "liveavatar_channel_sdk|AvatarAgent|developer_tts|voice_id|voiceId" . -g '*.py'
|
|
56
|
+
PYTHONPATH=... python -c 'import agent, asr, llm, tts; print("independent imports: ok")'
|
|
57
|
+
independent imports: ok
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The forbidden-symbol scan produced no matches and the combined command exited 0.
|
|
61
|
+
|
|
62
|
+
## Findings
|
|
63
|
+
|
|
64
|
+
- Existing offline coverage remains green; total suite is now 78 tests (the prior 64 plus focused additions and existing review additions).
|
|
65
|
+
- One sandbox-only run could not bind `127.0.0.1` for six aiohttp route tests; rerunning the identical full suite with localhost bind permission passed all 78 tests.
|
|
66
|
+
- Live smoke: **NOT RUN**, as requested. It requires real provider credentials/platform access.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Task 1: complete (staged implementation, 10 tests passed, review clean)
|
|
2
|
+
Task 2: complete (staged implementation, 7 tests passed, review clean)
|
|
3
|
+
Task 3: complete (staged implementation, 17 tests passed, review clean)
|
|
4
|
+
Task 4: complete (staged implementation, 19 tests passed, review clean)
|
|
5
|
+
Task 5: complete (staged implementation, 34 tests passed, review clean)
|
|
6
|
+
Task 6: complete (final implementation, 78 tests passed, whole-project review clean)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
### Task 1: Standalone configuration and LLM adapter
|
|
2
|
+
|
|
3
|
+
**Files:**
|
|
4
|
+
- Create: `/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/rtc-chat/agent.py`
|
|
5
|
+
- Create: `/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/rtc-chat/llm.py`
|
|
6
|
+
- Create: `/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/rtc-chat/requirements.txt`
|
|
7
|
+
- Create: `/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/rtc-chat/tests/test_llm.py`
|
|
8
|
+
|
|
9
|
+
**Interfaces:**
|
|
10
|
+
- Produces: `Settings.from_env() -> Settings`, `Settings.validate() -> None`.
|
|
11
|
+
- Produces: `LlmClient.stream_reply(user_text: str, on_delta: Callable[[str], Awaitable[None]]) -> str` and `reset() -> None`.
|
|
12
|
+
|
|
13
|
+
- [ ] **Step 1: Write failing configuration and LLM tests**
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
# tests/test_llm.py
|
|
17
|
+
import pytest
|
|
18
|
+
from agent import Settings
|
|
19
|
+
from llm import LlmClient
|
|
20
|
+
|
|
21
|
+
def test_settings_reject_missing_credentials():
|
|
22
|
+
settings = Settings("", "", None, "", "", "", "", "", "", 8080)
|
|
23
|
+
with pytest.raises(ValueError, match="LIVEAVATAR_API_KEY"):
|
|
24
|
+
settings.validate()
|
|
25
|
+
|
|
26
|
+
@pytest.mark.asyncio
|
|
27
|
+
async def test_stream_reply_forwards_deltas_and_retains_history(fake_openai):
|
|
28
|
+
client = LlmClient(api_key="key", base_url="https://llm.test", model="model",
|
|
29
|
+
system_prompt="system", client=fake_openai(["你", "好"]))
|
|
30
|
+
deltas = []
|
|
31
|
+
reply = await client.stream_reply("hello", deltas.append)
|
|
32
|
+
assert reply == "你好"
|
|
33
|
+
assert deltas == ["你", "好"]
|
|
34
|
+
assert client.messages[-2:] == [
|
|
35
|
+
{"role": "user", "content": "hello"},
|
|
36
|
+
{"role": "assistant", "content": "你好"},
|
|
37
|
+
]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- [ ] **Step 2: Run the focused tests and verify failure**
|
|
41
|
+
|
|
42
|
+
Run: `cd /Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/rtc-chat && pytest tests/test_llm.py -q`
|
|
43
|
+
|
|
44
|
+
Expected: import failure because `agent.py` and `llm.py` do not exist.
|
|
45
|
+
|
|
46
|
+
- [ ] **Step 3: Implement settings and the minimal streaming LLM client**
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
# llm.py core interface
|
|
50
|
+
class LlmClient:
|
|
51
|
+
def __init__(self, *, api_key, base_url, model, system_prompt, client=None):
|
|
52
|
+
self._client = client or AsyncOpenAI(api_key=api_key, base_url=base_url)
|
|
53
|
+
self._model = model
|
|
54
|
+
self._system_prompt = system_prompt
|
|
55
|
+
self.messages = [{"role": "system", "content": system_prompt}]
|
|
56
|
+
self._lock = asyncio.Lock()
|
|
57
|
+
|
|
58
|
+
async def stream_reply(self, user_text, on_delta):
|
|
59
|
+
async with self._lock:
|
|
60
|
+
self.messages.append({"role": "user", "content": user_text})
|
|
61
|
+
self.messages = [self.messages[0]] + self.messages[-20:]
|
|
62
|
+
stream = await self._client.chat.completions.create(
|
|
63
|
+
model=self._model, messages=self.messages, stream=True,
|
|
64
|
+
max_tokens=512, temperature=0.7)
|
|
65
|
+
parts = []
|
|
66
|
+
async for chunk in stream:
|
|
67
|
+
text = chunk.choices[0].delta.content
|
|
68
|
+
if text:
|
|
69
|
+
parts.append(text)
|
|
70
|
+
result = on_delta(text)
|
|
71
|
+
if inspect.isawaitable(result):
|
|
72
|
+
await result
|
|
73
|
+
reply = "".join(parts)
|
|
74
|
+
if reply.strip():
|
|
75
|
+
self.messages.append({"role": "assistant", "content": reply})
|
|
76
|
+
return reply
|
|
77
|
+
|
|
78
|
+
def reset(self):
|
|
79
|
+
self.messages = [{"role": "system", "content": self._system_prompt}]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Implement `Settings` as a frozen dataclass populated from the exact environment variables in the design. `validate()` checks required values in this order: `LIVEAVATAR_API_KEY`, `LIVEAVATAR_AVATAR_ID`, `DASHSCOPE_API_KEY`, `DEEPSEEK_API_KEY`.
|
|
83
|
+
|
|
84
|
+
Set `requirements.txt` to:
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
aiohttp>=3.9
|
|
88
|
+
dashscope>=1.25.11
|
|
89
|
+
openai>=1.0.0
|
|
90
|
+
pytest>=8.0
|
|
91
|
+
pytest-asyncio>=0.23
|
|
92
|
+
liveavatar-platform-rtc @ file:///Users/luffer/workspace/liveavatar-platform-rtc-python
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- [ ] **Step 4: Run focused tests**
|
|
96
|
+
|
|
97
|
+
Run: `pytest tests/test_llm.py -q`
|
|
98
|
+
|
|
99
|
+
Expected: all tests pass.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Task 1 Report
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
DONE_WITH_CONCERNS
|
|
6
|
+
|
|
7
|
+
Implementation is complete in the approved staging location `/tmp/rtc-chat-task1`. The requested destination is outside this agent's writable roots, so the main agent must copy the staged files into `/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/rtc-chat`.
|
|
8
|
+
|
|
9
|
+
## Files
|
|
10
|
+
|
|
11
|
+
- `/tmp/rtc-chat-task1/agent.py`
|
|
12
|
+
- `/tmp/rtc-chat-task1/llm.py`
|
|
13
|
+
- `/tmp/rtc-chat-task1/requirements.txt`
|
|
14
|
+
- `/tmp/rtc-chat-task1/tests/test_llm.py`
|
|
15
|
+
|
|
16
|
+
## TDD and test results
|
|
17
|
+
|
|
18
|
+
RED command:
|
|
19
|
+
|
|
20
|
+
`pytest tests/test_llm.py -q`
|
|
21
|
+
|
|
22
|
+
Raw result summary: exit 2 during collection with `ModuleNotFoundError: No module named 'agent'`, the expected missing-production-module failure before implementation.
|
|
23
|
+
|
|
24
|
+
GREEN command attempted after implementation:
|
|
25
|
+
|
|
26
|
+
`pytest tests/test_llm.py -q`
|
|
27
|
+
|
|
28
|
+
Raw result summary: exit 2 during collection. In this environment the `pytest` console entry point did not add the working directory to `sys.path`, so it still reported `ModuleNotFoundError: No module named 'agent'` even though `agent.py` exists.
|
|
29
|
+
|
|
30
|
+
Focused retry:
|
|
31
|
+
|
|
32
|
+
`PYTHONPATH=. pytest tests/test_llm.py -q`
|
|
33
|
+
|
|
34
|
+
Raw result summary: exit 2 during collection with `ModuleNotFoundError: No module named 'openai'`. Dependencies were not installed, per the instruction not to install anything.
|
|
35
|
+
|
|
36
|
+
Compilation check:
|
|
37
|
+
|
|
38
|
+
`python3 -m py_compile agent.py llm.py tests/test_llm.py`
|
|
39
|
+
|
|
40
|
+
Raw result summary: exit 0, no output.
|
|
41
|
+
|
|
42
|
+
## Review Follow-up (2026-07-15)
|
|
43
|
+
|
|
44
|
+
- Removed the remaining forbidden-field assertion from `/tmp/rtc-chat-task1/tests/test_llm.py` so the staged source tree contains no match for that identifier.
|
|
45
|
+
|
|
46
|
+
Command:
|
|
47
|
+
|
|
48
|
+
`/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/.venv/bin/python -m pytest -q /tmp/rtc-chat-task1/tests/test_llm.py`
|
|
49
|
+
|
|
50
|
+
Raw output:
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
.......... [100%]
|
|
54
|
+
10 passed in 0.25s
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Self-review
|
|
58
|
+
|
|
59
|
+
- `Settings` is a frozen dataclass with the ten-field positional interface required by the supplied test.
|
|
60
|
+
- `from_env()` reads the LiveAvatar, DashScope, DeepSeek, system prompt, and HTTP port environment variables with the specified demo defaults.
|
|
61
|
+
- `validate()` checks missing credentials in the required order: `LIVEAVATAR_API_KEY`, `LIVEAVATAR_AVATAR_ID`, `DASHSCOPE_API_KEY`, `DEEPSEEK_API_KEY`.
|
|
62
|
+
- `LlmClient` serializes turns with an asyncio lock, retains the system prompt plus the latest 20 conversation messages, forwards non-empty deltas to sync or async callbacks, stores non-blank assistant replies, and resets history to the system prompt.
|
|
63
|
+
- `requirements.txt` exactly matches the brief.
|
|
64
|
+
- No existing WebSocket chat, shared frontend, shared LLM client, or SDK source was modified.
|
|
65
|
+
|
|
66
|
+
## Concerns
|
|
67
|
+
|
|
68
|
+
- Main-agent authorization is required to copy the four staged files to the requested external directory.
|
|
69
|
+
- The focused test could not reach GREEN in the current interpreter because `openai` is not installed. After copying, run the brief's test command in the demo environment with requirements installed; if its `pytest` entry point has the same path behavior, use `PYTHONPATH=.` or `python -m pytest`.
|
|
70
|
+
|
|
71
|
+
## Review Fix (2026-07-15)
|
|
72
|
+
|
|
73
|
+
### Fixes
|
|
74
|
+
|
|
75
|
+
- Removed `Settings.liveavatar_voice_id` and the `LIVEAVATAR_VOICE_ID` environment read from `/tmp/rtc-chat-task1/agent.py`, because RTC start must not accept `voiceId`.
|
|
76
|
+
- Corrected history trimming in `/tmp/rtc-chat-task1/llm.py` to retain the original system message plus only the latest 20 non-system messages, without duplicating the system entry.
|
|
77
|
+
- Expanded `/tmp/rtc-chat-task1/tests/test_llm.py` to cover `Settings.from_env()`, all four required credential failures in validation order, `reset()`, async delta callbacks, and history trimming/system-message uniqueness.
|
|
78
|
+
|
|
79
|
+
### RED result
|
|
80
|
+
|
|
81
|
+
Command:
|
|
82
|
+
|
|
83
|
+
`/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/.venv/bin/python -m pytest -q /tmp/rtc-chat-task1/tests/test_llm.py`
|
|
84
|
+
|
|
85
|
+
Raw result summary: exit 1; `6 failed, 4 passed in 0.27s`. The failures showed that `Settings` still required the removed voice field. The short-history assertion was then tightened to enforce exactly one system message before applying the production fix.
|
|
86
|
+
|
|
87
|
+
### GREEN result
|
|
88
|
+
|
|
89
|
+
Command:
|
|
90
|
+
|
|
91
|
+
`/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/.venv/bin/python -m pytest -q /tmp/rtc-chat-task1/tests/test_llm.py`
|
|
92
|
+
|
|
93
|
+
Raw output:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
.......... [100%]
|
|
97
|
+
10 passed in 0.23s
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Compilation check:
|
|
101
|
+
|
|
102
|
+
`/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/.venv/bin/python -m py_compile /tmp/rtc-chat-task1/agent.py /tmp/rtc-chat-task1/llm.py /tmp/rtc-chat-task1/tests/test_llm.py`
|
|
103
|
+
|
|
104
|
+
Raw result summary: exit 0, no output.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
### Task 2: Qwen realtime ASR adapter
|
|
2
|
+
|
|
3
|
+
**Files:**
|
|
4
|
+
- Create: `/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/rtc-chat/asr.py`
|
|
5
|
+
- Create: `/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/rtc-chat/tests/test_asr.py`
|
|
6
|
+
|
|
7
|
+
**Interfaces:**
|
|
8
|
+
- Consumes: RTC `AudioFrame.data` containing 16 kHz mono PCM16.
|
|
9
|
+
- Produces: `QwenAsr.connect()`, `feed_audio(frame: AudioFrame)`, and `close()`.
|
|
10
|
+
- Produces callbacks `on_speech_started`, `on_speech_stopped`, `on_partial(text)`, `on_final(text)`, and `on_error(message)` on the owning asyncio loop.
|
|
11
|
+
|
|
12
|
+
- [ ] **Step 1: Write failing callback and audio tests**
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
@pytest.mark.asyncio
|
|
16
|
+
async def test_feed_audio_encodes_pcm(fake_conversation, audio_frame):
|
|
17
|
+
asr = make_asr(fake_conversation)
|
|
18
|
+
await asr.connect()
|
|
19
|
+
asr.feed_audio(audio_frame)
|
|
20
|
+
fake_conversation.append_audio.assert_called_once_with(
|
|
21
|
+
base64.b64encode(audio_frame.data.tobytes()).decode("ascii"))
|
|
22
|
+
|
|
23
|
+
@pytest.mark.asyncio
|
|
24
|
+
async def test_callback_dispatches_final_on_event_loop(callback, final_texts):
|
|
25
|
+
callback.on_event({"type": "conversation.item.input_audio_transcription.completed",
|
|
26
|
+
"transcript": "你好"})
|
|
27
|
+
await asyncio.sleep(0)
|
|
28
|
+
assert final_texts == ["你好"]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- [ ] **Step 2: Run tests and verify failure**
|
|
32
|
+
|
|
33
|
+
Run: `pytest tests/test_asr.py -q`
|
|
34
|
+
|
|
35
|
+
Expected: import failure for `asr.QwenAsr`.
|
|
36
|
+
|
|
37
|
+
- [ ] **Step 3: Implement the adapter around DashScope**
|
|
38
|
+
|
|
39
|
+
Use `OmniRealtimeConversation`, `OmniRealtimeCallback`, and `TranscriptionParams`. In `connect()`, run blocking `conversation.connect` with `asyncio.to_thread`, then configure:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
conversation.update_session(
|
|
43
|
+
output_modalities=[MultiModality.TEXT],
|
|
44
|
+
enable_turn_detection=True,
|
|
45
|
+
turn_detection_type="server_vad",
|
|
46
|
+
turn_detection_threshold=0.0,
|
|
47
|
+
turn_detection_silence_duration_ms=400,
|
|
48
|
+
enable_input_audio_transcription=True,
|
|
49
|
+
transcription_params=TranscriptionParams(
|
|
50
|
+
language="zh", sample_rate=16000, input_audio_format="pcm"),
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Implement callback dispatch with `asyncio.run_coroutine_threadsafe()` for coroutine callbacks and `loop.call_soon_threadsafe()` for synchronous callbacks. Ignore empty partial/final strings. Make `close()` idempotent and run the blocking provider close in a thread.
|
|
55
|
+
|
|
56
|
+
- [ ] **Step 4: Run focused tests**
|
|
57
|
+
|
|
58
|
+
Run: `pytest tests/test_asr.py -q`
|
|
59
|
+
|
|
60
|
+
Expected: all tests pass.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Task 2 Report: Qwen realtime ASR adapter
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Complete in the required staging area only:
|
|
6
|
+
|
|
7
|
+
- `/tmp/rtc-chat-task2/asr.py`
|
|
8
|
+
- `/tmp/rtc-chat-task2/tests/test_asr.py`
|
|
9
|
+
|
|
10
|
+
No Task 2 files were copied into the formal demo or SDK source trees.
|
|
11
|
+
|
|
12
|
+
## TDD evidence
|
|
13
|
+
|
|
14
|
+
### RED
|
|
15
|
+
|
|
16
|
+
Command:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
cd /tmp/rtc-chat-task2
|
|
20
|
+
/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/.venv/bin/python -m pytest tests/test_asr.py -q
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Result: collection failed with `ModuleNotFoundError: No module named 'asr'`, which is the expected missing-feature failure.
|
|
24
|
+
|
|
25
|
+
### GREEN
|
|
26
|
+
|
|
27
|
+
Command:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
cd /tmp/rtc-chat-task2
|
|
31
|
+
PYTHONPATH=/Users/luffer/workspace/liveavatar-platform-rtc-python/src /Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/.venv/bin/python -m pytest tests/test_asr.py -q
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Initial result before review fixes: `6 passed in 0.29s`.
|
|
35
|
+
|
|
36
|
+
### Final verification
|
|
37
|
+
|
|
38
|
+
Command:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
cd /tmp/rtc-chat-task2
|
|
42
|
+
PYTHONPATH=/Users/luffer/workspace/liveavatar-platform-rtc-python/src /Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/.venv/bin/python -m pytest tests/test_asr.py -q
|
|
43
|
+
/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/.venv/bin/python -m py_compile asr.py tests/test_asr.py
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Initial result before review fixes: `6 passed in 0.29s`; bytecode compilation
|
|
47
|
+
exited successfully.
|
|
48
|
+
|
|
49
|
+
## Implemented behavior
|
|
50
|
+
|
|
51
|
+
- Injected DashScope provider boundary for network-free tests.
|
|
52
|
+
- Blocking provider `connect()` and `close()` run through `asyncio.to_thread()`.
|
|
53
|
+
- Session configuration matches the brief: text modality, server VAD, threshold `0.0`, 400 ms silence, and Chinese 16 kHz PCM transcription.
|
|
54
|
+
- RTC audio uses `frame.to_pcm_bytes()` when available and falls back to `frame.data.tobytes()`, then base64-encodes for `append_audio()`.
|
|
55
|
+
- Speech start/stop, partial/final transcript, and provider error events map to owner callbacks.
|
|
56
|
+
- Provider-thread callbacks use `loop.call_soon_threadsafe()` to invoke on the
|
|
57
|
+
owning loop; returned awaitables are scheduled with `loop.create_task()`.
|
|
58
|
+
- Empty partial/final values and unknown events are ignored.
|
|
59
|
+
- Concurrent and repeated `close()` calls invoke provider close exactly once.
|
|
60
|
+
|
|
61
|
+
## Self-check
|
|
62
|
+
|
|
63
|
+
- Changes are limited to the required `/tmp` staging files and this report.
|
|
64
|
+
- No dependency was installed and no network call was made.
|
|
65
|
+
- Formal demo path `/Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/rtc-chat/asr.py` remains absent.
|
|
66
|
+
- Existing Task 1 files and old demo/SDK files were not modified.
|
|
67
|
+
|
|
68
|
+
## Concerns
|
|
69
|
+
|
|
70
|
+
- DashScope in the supplied virtual environment defines `TranscriptionParams` in `dashscope.audio.qwen_omni.omni_realtime` but does not export it from `dashscope.audio.qwen_omni`; the implementation uses that official submodule import for compatibility.
|
|
71
|
+
- Provider event schemas were implemented exactly from the supplied design/brief. A live credential smoke test was intentionally not run because this task requires network-free provider injection tests.
|
|
72
|
+
|
|
73
|
+
## Review Fix Verification
|
|
74
|
+
|
|
75
|
+
- Added the official Qwen ASR interim event contract
|
|
76
|
+
`conversation.item.input_audio_transcription.text` with `text`/`stash`, while
|
|
77
|
+
retaining compatible `.delta` handling.
|
|
78
|
+
- Added an async callable-object regression test for loop-side awaitable
|
|
79
|
+
scheduling.
|
|
80
|
+
- Fresh verification command run by the main agent:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
PYTHONPATH=/tmp/rtc-chat-task2:/Users/luffer/workspace/liveavatar-platform-rtc-python/src /Users/luffer/workspace/liveavatar-ws-integration-demo/python-agent/.venv/bin/python -m pytest -q /tmp/rtc-chat-task2/tests/test_asr.py
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Result: `7 passed in 0.22s`.
|