Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/cloud-agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ jobs:
INCLUDE_MAINTENANCE_CONTEXT: ${{ vars.INCLUDE_MAINTENANCE_CONTEXT || 'false' }}
INCLUDE_RUNBOOK_CONTEXT: ${{ vars.INCLUDE_RUNBOOK_CONTEXT || 'false' }}
DRY_RUN_ON_BUDGET_EXCEEDED: ${{ vars.DRY_RUN_ON_BUDGET_EXCEEDED || 'true' }}
AI_GATEWAY_FALLBACK_MODELS: ${{ vars.AI_GATEWAY_FALLBACK_MODELS || 'google/gemini-2.5-flash-lite' }}
AI_GATEWAY_FALLBACK_MODELS: ${{ vars.AI_GATEWAY_FALLBACK_MODELS || 'openai/gpt-5-nano' }}
AI_GATEWAY_SCREEN_FALLBACK_MODELS: ${{ vars.AI_GATEWAY_SCREEN_FALLBACK_MODELS || 'google/gemini-2.5-flash-lite' }}
AI_GATEWAY_MAX_OUTPUT_TOKENS: ${{ vars.AI_GATEWAY_MAX_OUTPUT_TOKENS || '32768' }}
# Tuned in-repo (repo Actions variables held stale lower values and
# silently clamped collection breadth; see docs/release-v0.19.0.md).
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- AI Gateway requests rely on the existing JSON-only prompt and strict parser instead of `response_format=json_object`, which DeepSeek V4 rejects through the Gateway.
- Added an explicit 32,768-token output ceiling and low-cost fallback recovery for HTTP 200 responses whose model content is truncated or otherwise invalid JSON.
- Normalize pure Markdown JSON fences before strict parsing so a valid fenced response does not spend a fallback call.
- Split fallback by workload: Flash Lite backs up Nano screening, while Nano backs up GPT-OSS synthesis after Flash Lite failed the weekly bilingual-content quality gate.
- A zero AI Gateway call budget now completes as a real dry run instead of failing the empty-update guard.

## v0.19.5 - 2026-07-18
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Daily notes are append-oriented. Use them to record high-signal items without fo

For fully Cloud Agent-driven operation, use `automation/daily.md`.

For true 24/7 cloud execution, `.github/workflows/cloud-agent.yml` uses GitHub Models by default with the built-in GitHub Actions `GITHUB_TOKEN`. The recommended cost-controlled mode uses Vercel AI Gateway directly with GPT-5 Nano for screening, GPT-OSS 120B for research and final synthesis, Gemini 2.5 Flash Lite only as a transient-failure fallback, and free public sources only; see `docs/cloud-agent.md`.
For true 24/7 cloud execution, `.github/workflows/cloud-agent.yml` uses GitHub Models by default with the built-in GitHub Actions `GITHUB_TOKEN`. The recommended cost-controlled mode uses Vercel AI Gateway directly with GPT-5 Nano for screening, GPT-OSS 120B for research and final synthesis, Nano again as the synthesis fallback, Gemini 2.5 Flash Lite only as the screening fallback, and free public sources only; see `docs/cloud-agent.md`.

Cloud mode uses source lanes, source-cache novelty tracking, scoring, source health, lane health, and structured telemetry. Daily, weekly, and monthly reports use nested bilingual pairs: each substantive item is a label bullet with `中文:` first and `English:` second as sub-bullets; short metadata fields stay on one line as `中文值(English value)`; URLs and product names are written once. At least 60% of substantive English lines must have a real Chinese counterpart.

Expand Down
5 changes: 3 additions & 2 deletions docs/cloud-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ REDDIT_SUBREDDITS=LocalLLaMA,MachineLearning,ClaudeAI,GithubCopilot,Cursor,ChatG
MAX_AI_GATEWAY_CALLS_PER_TASK=
MAX_PROMPT_CHARS=120000
DRY_RUN_ON_BUDGET_EXCEEDED=true
AI_GATEWAY_FALLBACK_MODELS=google/gemini-2.5-flash-lite
AI_GATEWAY_FALLBACK_MODELS=openai/gpt-5-nano
AI_GATEWAY_SCREEN_FALLBACK_MODELS=google/gemini-2.5-flash-lite
AI_GATEWAY_MAX_OUTPUT_TOKENS=32768
MAX_RELEASE_REPOS=20
MAX_RELEASES_PER_REPO=3
Expand Down Expand Up @@ -82,7 +83,7 @@ Model routing stays bounded but discovery-oriented:
- `promote-candidates`: GPT-OSS 120B automatically promotes at most 3 high-quality candidates from `research-log.md`.
- `weekly` and `monthly`: GPT-5 Nano screens public signals, then GPT-OSS 120B performs final synthesis (default `MAX_AI_GATEWAY_CALLS_PER_TASK=2`).

Gemini 2.5 Flash Lite is not part of the normal route. It is tried once only after a 408/429/5xx response, a transport timeout, an empty response, or malformed/truncated JSON. The 32,768-token ceiling is an output cap rather than prepaid usage; tokens are billed only when generated.
Fallback is tiered by workload and is not part of the normal route. A failed Nano screening call tries Gemini 2.5 Flash Lite; a failed GPT-OSS synthesis call tries Nano, which is more reliable for the long bilingual schema. Fallback is limited to a 408/429/5xx response, transport timeout, empty response, or malformed/truncated JSON. The 32,768-token ceiling is an output cap rather than prepaid usage; tokens are billed only when generated.

This keeps paid search calls at zero. Model usage is bounded by the fixed task route, `MAX_PUBLIC_SOURCE_ITEMS`, `MAX_AI_GATEWAY_CALLS_PER_TASK`, and `MAX_PROMPT_CHARS`.

Expand Down
5 changes: 3 additions & 2 deletions docs/subscription-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ MAX_PUBLIC_SOURCE_ITEMS=
PUBLIC_SOURCE_COLLECTION=true
MAX_PROMPT_CHARS=120000
DRY_RUN_ON_BUDGET_EXCEEDED=true
AI_GATEWAY_FALLBACK_MODELS=google/gemini-2.5-flash-lite
AI_GATEWAY_FALLBACK_MODELS=openai/gpt-5-nano
AI_GATEWAY_SCREEN_FALLBACK_MODELS=google/gemini-2.5-flash-lite
AI_GATEWAY_MAX_OUTPUT_TOKENS=32768
MAX_RELEASE_REPOS=12
MAX_RELEASES_PER_REPO=2
Expand All @@ -108,4 +109,4 @@ Behavior:
- Promote-candidates runs automatically promote at most 3 high-quality candidates.
- Daily runs use GPT-5 Nano for screening and GPT-OSS 120B for final updates.
- Weekly/monthly runs use GPT-5 Nano for screening, then GPT-OSS 120B for final synthesis (default `MAX_AI_GATEWAY_CALLS_PER_TASK=2`).
- Gemini 2.5 Flash Lite is tried only for transient HTTP/transport failures or invalid/truncated JSON, keeping fallback output at the same low price tier as the primary route.
- Fallback remains workload-aware and low-cost: Flash Lite backs up Nano screening, while Nano backs up GPT-OSS long-form synthesis after transient HTTP/transport failures or invalid/truncated JSON.
15 changes: 12 additions & 3 deletions scripts/cloud_agent_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -5445,9 +5445,16 @@ def ai_gateway_headers() -> dict[str, str]:


def ai_gateway_fallback_models(model: str) -> list[str]:
fallback = split_env_list(
"AI_GATEWAY_FALLBACK_MODELS", [DEFAULT_AI_GATEWAY_FALLBACK_MODEL]
)
cheap = os.environ.get("CHEAP_SCREEN_MODEL", DEFAULT_CHEAP_SCREEN_MODEL)
if model == cheap:
fallback = split_env_list(
"AI_GATEWAY_SCREEN_FALLBACK_MODELS", [DEFAULT_AI_GATEWAY_FALLBACK_MODEL]
)
else:
# Reuse Nano for synthesis recovery: it is cheaper than Mini, retains a
# long output window, and avoids asking Flash Lite to write the long
# bilingual reports that it failed the production quality gate on.
fallback = split_env_list("AI_GATEWAY_FALLBACK_MODELS", [DEFAULT_CHEAP_SCREEN_MODEL])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep synthesis fallbacks on the long timeout

When a daily/weekly/monthly synthesis call falls back through this new Nano default, call_ai_gateway_model still uses model_call_timeout(candidate_model), and model_call_timeout treats openai/gpt-5-nano as the cheap screening model with the 300s SCREEN_MODEL_TIMEOUT. That means the long bilingual synthesis recovery path can be killed after five minutes even though the primary GPT-OSS synthesis call gets the 900s synthesis timeout; use the workload/original model to choose the timeout instead of the fallback model name.

Useful? React with 👍 / 👎.

models = [model]
for item in fallback:
if item not in models:
Expand Down Expand Up @@ -5495,6 +5502,8 @@ def call_ai_gateway_model(prompt: str, model: str) -> dict[str, Any]:
audit_model(candidate_model)
if candidate_model != model:
RUN_AUDIT["fallbacks"].append(f"{model}->{candidate_model}")
reason = last_error.split(":", 1)[0] if last_error else "transient failure"
print(f"AI Gateway fallback {model}->{candidate_model}: {reason}")
if attempt > 0:
# Brief backoff before retrying a transient failure.
time.sleep(min(8, 2 ** attempt))
Expand Down
11 changes: 11 additions & 0 deletions tests/test_cloud_agent_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ def test_ai_gateway_default_model_route_uses_nano_and_gpt_oss(self) -> None:
["openai/gpt-oss-120b"],
)

def test_ai_gateway_default_fallbacks_are_tiered_by_workload(self) -> None:
with mock.patch.dict(os.environ, {}, clear=True):
self.assertEqual(
cloud_agent_runner.ai_gateway_fallback_models("openai/gpt-5-nano"),
["openai/gpt-5-nano", "google/gemini-2.5-flash-lite"],
)
self.assertEqual(
cloud_agent_runner.ai_gateway_fallback_models("openai/gpt-oss-120b"),
["openai/gpt-oss-120b", "openai/gpt-5-nano"],
)

def test_auto_tasks_include_candidate_promotion_on_sunday(self) -> None:
tasks = cloud_agent_runner.auto_tasks(cloud_agent_runner.parse_date("2026-07-05"))
self.assertIn("daily", tasks)
Expand Down
Loading