Cut Gemini cost: default to flash-lite + optional reply drafting - #39
Conversation
Input-token cost on the every-5-minutes email pipeline was dominated by the premium gemini-3.5-flash model. Two changes drastically reduce it: - Default GEMINI_MODEL to gemini-2.0-flash-lite (~10x cheaper input tokens than gemini-3.5-flash) for both analyze_email and standup. The env var still overrides for higher accuracy when needed. - Add ENABLE_DRAFT_REPLIES (default true, mirrors ENABLE_ACTION_ITEMS). When false, the large REPLY section is dropped from the analyze_email prompt so the model never classifies replies or drafts one — cutting both input and output tokens — and no Gmail drafts are created. Calendar events, action items, and context updates are unaffected. Adds deterministic unit tests for prompt-section gating and updates README and .env.example. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSnZNqUEh8uDyMCPDmzBTa
There was a problem hiding this comment.
Code Review
This pull request switches the default model to gemini-2.0-flash-lite and introduces a new ENABLE_DRAFT_REPLIES configuration flag to conditionally disable reply drafting and reduce API costs. Unit tests are also added to verify the prompt-building logic. The reviewer pointed out that when reply drafting is disabled, the underlying Pydantic schema EmailAnalysis still requires the needs_reply field, which may cause validation errors or hallucinated outputs from the model. It is recommended to set a default value of False for needs_reply in the schema.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| model: str = GEMINI_MODEL, | ||
| user_context: Optional[dict] = None, | ||
| extract_action_items: bool = True, | ||
| draft_replies: bool = True, |
There was a problem hiding this comment.
When draft_replies is set to False, the REPLY section is omitted from the system prompt. However, the EmailAnalysis Pydantic model (used as the response_schema for Gemini) defines needs_reply as a required boolean field:
class EmailAnalysis(BaseModel):
...
needs_reply: boolBecause it is required, the Gemini model is forced to output needs_reply in the JSON response, even though the system prompt contains no instructions or context on how to evaluate it. This can lead to unpredictable model behavior, validation errors, or arbitrary/hallucinated values.
To resolve this, please update the EmailAnalysis class (around line 122) to provide a default value for needs_reply:
needs_reply: bool = False…tions When ENABLE_DRAFT_REPLIES=false drops the REPLY section from the prompt, needs_reply as a required schema field still forced the model to emit it with no guiding instructions. Give it a default of False so the model can omit it cleanly; main.py already gates draft creation on the flag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSnZNqUEh8uDyMCPDmzBTa
Summary
Input-token cost on the every-5-minutes email pipeline was dominated by the premium
gemini-3.5-flashmodel. Two changes drastically reduce it:GEMINI_MODEL→gemini-2.0-flash-lite(~10× cheaper input tokens thangemini-3.5-flash) for bothanalyze_emailand the standup. The env var still overrides for higher accuracy when needed. This is the single biggest lever — it multiplies down the cost of the fixed system prompt, the injected user context, and every email body.ENABLE_DRAFT_REPLIESflag (defaulttrue, mirrors the existingENABLE_ACTION_ITEMS). When set tofalse, the large REPLY section is dropped from theanalyze_emailprompt entirely, so the model never classifies replies or drafts one — cutting both input and output tokens on every email — and no Gmail drafts are created. Calendar events, action items, and context updates are unaffected.To go maximally cheap, set both:
Changes
assistant/event_extractor.py:GEMINI_MODELdefault →gemini-2.0-flash-lite;build_system_prompt()andanalyze_email()gain adraft_repliesparam that omits the REPLY section.main.py: readsENABLE_DRAFT_REPLIES, passes it through, skips draft creation (and theget_user_emailcall) when off; standup default model updated to match.tests/test_prompt_building.py: deterministic unit tests (no API key) verifying each flag drops its section and sections renumber contiguously.README.md,.env.example: document the model default change and the new flag.Test plan
pytest tests/— 24 passed, 8 skipped (Gemini eval tests skip without a key locally)ENABLE_DRAFT_REPLIES=falseand confirm in Cloud Logging thatanalyze_emailprompt tokens drop and no drafts are created🤖 Generated with Claude Code
https://claude.ai/code/session_01CSnZNqUEh8uDyMCPDmzBTa
Generated by Claude Code