Commit d449661
authored
feat(apps): add TEN tool-call and Agora custom-llm Moss samples with offline bench (#507)
## Intent
PR #507 adds Agora/TEN Moss integration samples: (1) TEN tool-call graph
voice_assistant_tools alongside the ambient voice_assistant default,
where main_control self-registers search_knowledge_base and handles
tool_call in-process via MossSessionManager; (2) an offline bench under
apps/ten-moss/bench (ambient/tool-call/no-Moss arms, --echo-grounding
zero-LLM smoke); (3) a custom-llm middleware app
apps/agora-custom-llm-moss forked from Agora's recipe with ambient and
tool modes over the same 10-FAQ corpus, Bearer auth, and a zero-key
doctor path. Samples are meant to be stranger-copyable.
This run validates the fix commit on top of that PR, which closes three
post-review threads:
- TEN stale retrieval note: _query_moss() returned '' on exception but
left _last_grounding/_last_sdk_ms from a prior successful search, so a
failed second search in the same turn replayed the earlier hit in the
retrieval note while the tool result was empty. Fix: reset
_last_grounding/_last_sdk_ms at the top of _query_moss before touching
the backend, and have _on_tool_call send the note the grounding it just
received. Regression in apps/ten-moss/tests/test_retrieval_note.py
drives _on_tool_call through a success then a failure and asserts the
second note reads 'no match'.
- Custom-llm doctor env leak: run_doctor() is called in-process by
tests; it flipped MOCK to '1' then '0' and never restored it (and did
not fully own CUSTOM_LLM_API_KEY restoration). Fix: snapshot MOCK and
CUSTOM_LLM_API_KEY before the first create_app() and restore both in an
outer finally, including the 'was absent' case; the temporary key
removal for the unset-key request stays separate. Regressions assert
both preset and originally-absent vars are restored.
Scope is intentionally limited to these three threads; no behavior
changes beyond them. Do not merge.
## What Changed
- Added a TEN tool-call graph (`voice_assistant_tools`) beside the
ambient `voice_assistant` default, where `main_control` self-registers
`search_knowledge_base` and handles `tool_call` in-process via
`MossSessionManager`; opened Moss on the first custom-llm request,
treated blank `moss_mode` as ambient, and reset
`_last_grounding`/`_last_sdk_ms` at the top of `_query_moss` so a failed
second search no longer replays a prior hit in the retrieval note.
- Added an offline bench under `apps/ten-moss/bench` (ambient /
tool-call / no-Moss arms with an `--echo-grounding` zero-LLM smoke path)
plus graph and retrieval-note regression tests.
- Added the `apps/agora-custom-llm-moss` middleware app forked from
Agora's custom-llm recipe with ambient and tool modes over the shared
10-FAQ corpus, Bearer auth, `.env` loading, and a zero-key doctor path
that snapshots and restores `MOCK`/`CUSTOM_LLM_API_KEY`; wired CI and
refreshed the README/AGENTS docs.
## Risk Assessment
✅ Low: The fix commit is tightly scoped to the three declared
post-review threads, each fix is correct and backed by a targeted
regression test, and no defects, regressions, or intent contradictions
were found across the changed source.
## Testing
`Ran the two regression suites on the target commit (all 12 ten-moss and
14 custom-llm tests pass) after building a fresh venv with
fastapi/httpx/pydantic/pytest-asyncio. To prove the regressions actually
guard the fixed bugs, I reverted each source fix to its pre-fix version
(da1e5ba) and confirmed the tests fail with the exact described symptoms
(retrieval note replaying the prior "3-5 business days" hit; doctor
leaking MOCK='0'), then restored. I also captured two product-level CLI
transcripts as reviewer evidence: the TEN retrieval-note transcript
shows turn 1 (Moss hit) vs turn 2 (failed second search -> "no match"
note with empty tool result, no replay), and the doctor transcript shows
both modes + bearer checks running and MOCK/CUSTOM_LLM_API_KEY restored
to their preset values afterward. These are CLI/API surfaces with no
rendered UI, so text transcripts are the appropriate end-user artifact.
Worktree cleaned of pytest/__pycache__ artifacts; evidence left in the
dedicated directory.`
<details>
<summary>Evidence: TEN retrieval-note fix: turn 1 hit vs turn 2 failed
search (no stale replay)</summary>
<code>=== TURN 1 (Moss hit) === retrieval note : '🔎 Moss ·
retrieved in 12 ms (SDK time_taken_ms)\n\nRefunds land in 3-5 business
days.' tool result : {"type": "llmresult",
"content": "Refunds land in 3-5 business
days."} === TURN 2 (second search FAILS mid-turn)
=== retrieval note : '🔎 Moss · retrieved in n/a ms (SDK
time_taken_ms) — no match' tool result : {"type":
"llmresult", "content": ""} replays
first hit in note? False (must be False) note says no match? True
(must be True) note & tool result agree? True (empty result,
must be True)</code>
```text
=== TURN 1 (Moss hit) ===
retrieval note : '🔎 Moss · retrieved in 12 ms (SDK time_taken_ms)\n\nRefunds land in 3-5 business days.'
tool result : {"type": "llmresult", "content": "Refunds land in 3-5 business days."}
=== TURN 2 (second search FAILS mid-turn) ===
retrieval note : '🔎 Moss · retrieved in n/a ms (SDK time_taken_ms) — no match'
tool result : {"type": "llmresult", "content": ""}
replays first hit in note? False (must be False)
note says no match? True (must be True)
note & tool result agree? True (empty result, must be True)
```
</details>
<details>
<summary>Evidence: custom-llm --doctor: both modes + bearer checks pass,
preset env restored (no leak)</summary>
<code>BEFORE MOCK='preset'
CUSTOM_LLM_API_KEY='preset-key' doctor ambient:
ok doctor tool: ok doctor bearer: rejected missing
Authorization doctor bearer: rejected any token while
CUSTOM_LLM_API_KEY is unset doctor: ok AFTER
MOCK='preset' CUSTOM_LLM_API_KEY='preset-key' ENV
RESTORED: no leak</code>
```text
/tmp/nm-venv-3b0/lib/python3.12/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
INFO:llm:Moss disabled (set MOSS_PROJECT_ID / MOSS_PROJECT_KEY / MOSS_INDEX_NAME)
INFO:httpx:HTTP Request: POST http://testserver/chat/completions "HTTP/1.1 200 OK"
INFO:llm:Moss disabled (set MOSS_PROJECT_ID / MOSS_PROJECT_KEY / MOSS_INDEX_NAME)
INFO:llm:[retrieval-latency] tool_called=true (mock stub)
INFO:httpx:HTTP Request: POST http://testserver/chat/completions "HTTP/1.1 200 OK"
INFO:llm:Moss disabled (set MOSS_PROJECT_ID / MOSS_PROJECT_KEY / MOSS_INDEX_NAME)
INFO:httpx:HTTP Request: POST http://testserver/chat/completions "HTTP/1.1 401 Unauthorized"
INFO:llm:Moss disabled (set MOSS_PROJECT_ID / MOSS_PROJECT_KEY / MOSS_INDEX_NAME)
INFO:httpx:HTTP Request: POST http://testserver/chat/completions "HTTP/1.1 401 Unauthorized"
BEFORE MOCK='preset' CUSTOM_LLM_API_KEY='preset-key'
doctor ambient: ok
doctor tool: ok
doctor bearer: rejected missing Authorization
doctor bearer: rejected any token while CUSTOM_LLM_API_KEY is unset
doctor: ok
AFTER MOCK='preset' CUSTOM_LLM_API_KEY='preset-key'
ENV RESTORED: no leak
```
</details>
## Pipeline
Updates from [git push
no-mistakes](https://github.com/kunchenguid/no-mistakes)
<details>
<summary>✅ **intent** - passed</summary>
✅ No issues found.
</details>
<details>
<summary>✅ **Rebase** - passed</summary>
✅ No issues found.
</details>
<details>
<summary>✅ **Review** - passed</summary>
✅ No issues found.
</details>
<details>
<summary>✅ **Test** - passed</summary>
✅ No issues found.
- <code>`pytest apps/ten-moss/tests/test_retrieval_note.py` (pass on
target)</code>
- <code>`pytest apps/ten-moss` full suite - 12 passed (tests +
bench)</code>
- <code>`pytest apps/agora-custom-llm-moss/tests/test_llm.py` - 14
passed incl. test_doctor_restores_preset_env /
test_doctor_restores_absent_env</code>
- <code>Bug-guard proof: `git checkout da1e5ba -- extension.py` then ran
retrieval-note test -> FAILED replaying first hit; restored</code>
- <code>Bug-guard proof: `git checkout da1e5ba -- llm.py` then ran
env-restore tests -> 2 FAILED (MOCK left as '0');
restored</code>
- <code>Manual E2E: drove `MainControlExtension._on_tool_call` through
success-then-failure, captured both retrieval notes and tool
results</code>
- <code>Manual E2E: ran `run_doctor()` under preset
MOCK=preset/CUSTOM_LLM_API_KEY=preset-key and asserted both restored
afterward</code>
</details>
<details>
<summary>✅ **Document** - passed</summary>
✅ No issues found.
</details>
<details>
<summary>✅ **Lint** - passed</summary>
✅ No issues found.
</details>
<details>
<summary>✅ **Push** - passed</summary>
✅ No issues found.
</details>1 parent de26a4b commit d449661
27 files changed
Lines changed: 1861 additions & 115 deletions
File tree
- .github/workflows
- apps
- agora-custom-llm-moss
- data
- server
- src
- tests
- ten-moss
- bench
- tenapp
- ten_packages/extension/main_python
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
179 | 179 | | |
180 | 180 | | |
181 | 181 | | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| 43 | + | |
43 | 44 | | |
44 | 45 | | |
45 | 46 | | |
| |||
111 | 112 | | |
112 | 113 | | |
113 | 114 | | |
| 115 | + | |
114 | 116 | | |
115 | 117 | | |
116 | 118 | | |
117 | 119 | | |
118 | 120 | | |
119 | | - | |
| 121 | + | |
120 | 122 | | |
121 | 123 | | |
122 | 124 | | |
| |||
273 | 275 | | |
274 | 276 | | |
275 | 277 | | |
| 278 | + | |
276 | 279 | | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
160 | 160 | | |
161 | 161 | | |
162 | 162 | | |
| 163 | + | |
| 164 | + | |
163 | 165 | | |
164 | 166 | | |
165 | 167 | | |
| |||
238 | 240 | | |
239 | 241 | | |
240 | 242 | | |
241 | | - | |
242 | | - | |
| 243 | + | |
| 244 | + | |
243 | 245 | | |
244 | 246 | | |
245 | 247 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
0 commit comments