feat(email): engine=llm triage API + remove body clipping (#1452, #1539)#1590
Conversation
, #1539) Before: the email triage API was heuristic-only; the engine=llm path added in PR #1547 patched src/gaia/api/email_routes.py which was deleted when the email agent migrated to the hub (#1520). Body text was silently truncated to 4 000 characters before reaching the local model, hiding signal in long threads. After: the hub api_routes.py gains the engine=llm path (asyncio.to_thread + LLMTriageError/EmailSummarizeError → 502 translation); body clipping is removed from llm_triage.py and summarize_tools.py so the full text reaches the model; thread joining is reversed to newest-first so recent messages get priority. EmailTriageResult gains the message_id echo (#1551 contract change, ported here).
SummaryClean, well-tested port of the Issues🟡 Important"byte-identical" claim is inaccurate for threads ( The newest-first reversal was applied to the heuristic thread path (
Unbounded prompt growth has no context-window guard ( Removing 🟢 Minor
(and the same for the Contract field description is slightly off ( The description says the value echoes " Test gap: the 502 mapping is uncovered. The new tests cover the happy path, service-level Strengths
VerdictApprove with suggestions. No security or correctness blockers. The only must-fix before merge is reconciling the "byte-identical" docstring (and PR description) with the now-changed default thread ordering — pick one of: soften the claim, or scope the reversal to the LLM path. The rest are minor and safe to apply in the same pass. |
|
🟡 Either the docstring overclaims and should say the LLM path applies to |
…starts (amd#1620) **Why this matters** The documented Milestone-40 partner recipe — `pip install 'amd-gaia[api]' gaia-agent-email`, then `gaia api` — crashed at server startup with `ModuleNotFoundError: No module named 'keyring'`, before serving a single request. `gaia api` auto-mounts the email wheel's REST router, and that import chain reaches a module-level `import keyring` in `gaia.connectors.store` (also hit at request time via `connected_mailbox_providers()`) — but `keyring` was declared only in the `[ui]`/`[dev]` extras, never `[api]`. So the one install combo the partner path depends on couldn't even boot; 0.20.1 only masks it because its server doesn't mount the email router yet. After this change `[api]` carries `keyring` (same pin as `[ui]`/`[dev]`), so the documented install starts and serves local LLM triage with zero manual installs. Needs to land before the v0.21.0 tag. A packaging guard (`tests/unit/test_api_extras.py`, mirroring `test_ui_extras.py`) fails if `[api]` ever drops keyring again. **Test plan** - [x] Guard test is red on `main` (keyring absent from `[api]`), green with the fix. - [x] `pytest tests/unit/test_api_extras.py tests/unit/test_packaging.py tests/unit/test_ui_extras.py` → 13 passed; `python util/lint.py --all` → clean. - [x] Real-world, Linux + Radeon dGPU, local Lemonade (Gemma-4-E4B), fresh venvs via the documented `git+` recipe: - **Before (`main`):** `from gaia.api.openai_server import app` → `ModuleNotFoundError: No module named 'keyring'` at `store.py:38` (keyring not installed). - **After (this branch):** keyring 25.7.0 pulled by `[api]` → import clean → uvicorn boots → `POST /v1/email/triage` → **200** with LLM triage (category `actionable`, summary echoing the planted `$4,820 / INV-90871 / Thursday 3pm` facts, action item + draft reply). Out of scope: AC3 (partner guide stating the exact install line) is tracked by amd#1597, and depends on amd#1601 publishing the `gaia-agent-email` wheel. Closes amd#1617. Related: amd#1602, amd#1590, amd#1597, amd#915, amd#1179. --------- Co-authored-by: Tomasz Iniewicz <heaters-nays0p@icloud.com>
The
engine=llmpath added for #1452 was written againstsrc/gaia/api/email_routes.py, which the hub migration (#1520) deleted — leaving the feature stranded on a conflicting branch. Body text was silently truncated to 4 000 characters before reaching the local model, cutting signal from long emails and threads (#1539 observation).This PR ports both fixes to the hub's canonical location.
After:
POST /v1/email/triage?engine=llmworks end-to-end — low-confidence results are escalated to the local Lemonade model (Gemma-4-E4B) with the full email body and a 4 096-token output cap so chain-of-thought reasoning fits. Thread bodies are now joined newest-first.EmailTriageResult.message_idechoes the request ID back to the caller.Test plan
uv run python -m pytest hub/agents/python/email/tests/ -x— 11 tests pass (including body-not-clipped and newest-first ordering assertions)curl -s localhost:4200/v1/email/triage?engine=llm -d '...'returns 200 withresult.categoryfrom LLM (not 502)curl ... ?engine=openaireturns 422Closes #1452
Closes #1539