Skip to content

dev → master: BLS macro tools + AI-readable network error classification#172

Merged
luokerenx4 merged 2 commits intomasterfrom
dev
May 9, 2026
Merged

dev → master: BLS macro tools + AI-readable network error classification#172
luokerenx4 merged 2 commits intomasterfrom
dev

Conversation

@luokerenx4
Copy link
Copy Markdown
Contributor

Summary

Two follow-ups to the FRED + EIA macro-data work that landed in PR #171:

  1. BLS plugged into the same economy* AI tool namespace — third macro provider (after FRED + EIA), exposed as economyBlsSearch + economyBlsSeries. Fixes a 5th instance of the "BLS returns string sentinels for missing data, fetcher pushes NaN to schema" pattern that bit FRED / EIA earlier.
  2. AI-readable network errors — a network-layer fetch failure (DNS / TLS / proxy / routing) now surfaces to the LLM as NetworkUnreachableError with explicit "do not retry" guidance, instead of the indistinguishable-from-flake OpenBBError("Request failed (TypeError: fetch failed): ..."). Motivation: users behind Clash-style fake-ip proxies routinely hit this on US gov hosts (BLS in particular), and AI agents need to recognize "user network problem, escalate" vs "transient, retry".

Per-session contributions

2026-05-09 — BLS macro tools + AI-readable network errors

  • feat(tool): expose BLS search + series as economy tools, fix NaN sneak-through (7fdcc71)
    • Adds economyBlsSearch (curated BLS catalog filter) and economyBlsSeries (timeseries fetch). EconomyClientLike extended with getBlsSearch / getBlsSeries; createEconomyTools signature unchanged from PR dev → master: ephemeral UTAs + IBKR-as-truth scope correction + FRED tools #171.
    • 5th macro-data string→number fix: bls-series.ts was passing parseFloat('-') (BLS's sentinel for missing observations, e.g. UNRATE 2025-10 footnoted as "Data unavailable due to the 2025 lapse in appropriations") through to the schema as NaN, killing entire batches. Fix: skip NaN rows. Same shape as the EIA STEO + Petroleum Status fixes from PR dev → master: ephemeral UTAs + IBKR-as-truth scope correction + FRED tools #171.
    • Tool-layer + e2e tests added; multi-series merge format documented in tool description because it differs from FRED (BLS is long form one-row-per-(date,series_id), FRED pivots wide).
  • feat(opentypebb): classify network-unreachable errors for AI tool callers (39b014a)
    • New NetworkUnreachableError (extends OpenBBError) thrown by amakeRequest when fetch fails at the network layer (TypeError("fetch failed") with cause-code ENOTFOUND / ECONNREFUSED / ETIMEDOUT / TLS errors). Message text is intentionally LLM-targeted prose: contains NETWORK_UNREACHABLE token, the host, the underlying cause code, "Do NOT retry", and a user-side fix hint.
    • Concrete payoff: when a user runs OpenAlice behind a Clash-family proxy with fake-ip routing, hosts that aren't in the rule set TCP-connect to the local fake IP and die in TLS handshake. Pre-fix the AI sees Request failed (TypeError: fetch failed) and pattern-matches as transient flake → retries N times same-args. Post-fix the AI sees the classified error on first call and tells the user to check their proxy.
    • 6 helper unit tests pin the boundary: TypeError("fetch failed") + ENOTFOUND cause classify; HTTP 4xx, DOMException timeout, generic Error stay in the existing OpenBBError paths. End-to-end verified by stubbing fetch in a tool-execute call and observing the AI-visible message.
    • All providers (FRED / BLS / EIA / FMP / yfinance / etc.) benefit because they all route through the central amakeRequest helper.

Full commit log

39b014a feat(opentypebb): classify network-unreachable errors for AI tool callers
7fdcc71 feat(tool): expose BLS search + series as economy tools, fix NaN sneak-through

Test plan

  • npx tsc --noEmit clean (root + ui workspaces)
  • pnpm vitest run — 76 files / 1478 tests passing (was 1463 in PR dev → master: ephemeral UTAs + IBKR-as-truth scope correction + FRED tools #171; +9 BLS tool spec cases + 6 helpers spec cases)
  • pnpm vitest run --config vitest.e2e.config.ts src/domain/market-data/__test__/e2e/ — 11 cases now (5 FRED + 3 EIA + 3 BLS), all green against live APIs with the user's keys
  • Live tool-layer smell test for BLS via the SDK + tool composition: UNRATE Jan 2025 → Apr 2026 returns clean monthly rows, 2025-10 government-shutdown dropout silently skipped; CPI + NFP multi-series long-form merge correct
  • Network-error classifier verified end-to-end: stubbed fetch to throw TypeError("fetch failed") with cause-code ENOTFOUND, walked through tool.economyBlsSeries.execute → caught at the boundary as NetworkUnreachableError with the AI-visible message containing NETWORK_UNREACHABLE, the host, "Do NOT retry", and the user-fix hint
  • After merge: hit economyBlsSearch / economyBlsSeries from an Agent SDK session — should return real data
  • After merge (optional): toggle the user's local proxy to drop a domain into the fake-ip black-hole region, observe that the AI gracefully tells the user to fix their network instead of retrying

🤖 Generated with Claude Code

Ame and others added 2 commits May 9, 2026 11:21
…k-through

Third macro source plugged into the economy tool namespace. AI agents
can now query BLS catalog + observations the same way they query FRED
or EIA, with provider name pinned at the tool boundary.

Tools added:

- economyBlsSearch  — keyword filter against the curated BLS catalog
                      (BLS itself has no search API; the catalog is a
                      hand-maintained list in the typebb fetcher)
- economyBlsSeries  — observations for one or more BLS series ids,
                      comma-separated for multi-series. Returns long
                      form (one row per date+series_id) — different
                      from FRED's pivot-by-date wide form, documented
                      in the tool description so the LLM doesn't
                      pattern-match incorrectly from FRED.

Wiring:

- EconomyClientLike: added getBlsSearch + getBlsSeries (SDK already
  implements them at /survey/bls_*).
- tool/economy.ts: BLS_PROVIDER pin + 2 new tool factories.
- tool/economy.spec.ts: extended mock and added 9 schema /
  passthrough / cross-client-isolation cases.

Bug fixed (5th in the macro-data string→number family):

- bls-series.ts: BLS returns "-" for unavailable observations
  (e.g. UNRATE 2025-10 dropped during the 2025 lapse in
  appropriations — footnote text in the wire response confirms).
  parseFloat("-") returns NaN; the schema rejects NaN with
  "Expected number, received nan" and the whole batch dies. Same
  shape as the EIA fix: skip NaN rows instead of pushing them
  through. Verified end-to-end against the live API; the 2025-10
  row is now silently dropped rather than poisoning the result.

E2e: added a BLS describe block to market-data.e2e.spec.ts
(test-provider button + bls_search via /economy/survey/bls_search +
bls_series with explicit Number.isFinite check on every value to
catch any future NaN regression). 11 e2e cases now (5 FRED + 3 EIA
+ 3 BLS) all green against live APIs.

Verified live with the user's BLS key:
- BlsSearch "CPI": top 5 CPI sub-series (All Items, Core, Food at Home,
  Gasoline, New Vehicles)
- UNRATE Jan 2025–Apr 2026: 4.3-4.5% range, stable; 2025-10 dropout
  silently skipped
- CPI + Nonfarm Payrolls multi: CPI-U 2026-03 = 330.213, NFP 2026-04
  = 158.7M total nonfarm payrolls

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lers

Tool errors propagate verbatim into the AI's next turn (Vercel AI SDK
forwards the raw thrown value into the tool-result message). When the
underlying fetch fails at the network layer — DNS / TLS / proxy /
routing — we were throwing the generic OpenBBError("Request failed
(TypeError: fetch failed): ..."), which looks indistinguishable to an
LLM from a transient flake. Models retry, hit the same wall again,
burn tokens, and never tell the user to check their network.

This change introduces a NetworkUnreachableError that classifies the
fetch-failure case and gives the LLM unambiguous signal:

  NETWORK_UNREACHABLE: cannot reach <host> from this machine (<cause>).
  This is a network-layer failure (DNS / routing / TLS / proxy), not a
  provider error — the provider's API may well be operational, the
  connection from this network cannot complete. Do NOT retry the same
  call; ask the user to check their VPN / proxy routing for this
  hostname, or fall back to a different data source.

Concrete impact on the BLS / EIA / FRED tools just landed: when a user
runs OpenAlice behind a Clash-style proxy with fake-ip routing, hosts
that aren't in the rule set TCP-connect to the local fake IP, then die
in TLS handshake — fetch surfaces this as TypeError("fetch failed")
with cause.code = ECONNRESET / SSL_ERROR_SYSCALL / ENOTFOUND. The AI
now sees the new classified error instead of "fetch failed", recognizes
this as user-fixable, and tells them so on the first failure rather
than after N silent retries.

Implementation:

- packages/opentypebb/src/core/provider/utils/errors.ts: new
  NetworkUnreachableError extending OpenBBError. Carries the host as a
  field for callers that want to render structured errors. Message
  string is intentionally LLM-targeted prose, not just a code.

- packages/opentypebb/src/core/provider/utils/helpers.ts: amakeRequest
  now branches on isFetchNetworkFailure (TypeError + "fetch failed"
  message — the Node 18+ fetch wrapper's signature for network-layer
  failures). Cause-code extraction (ENOTFOUND / ECONNREFUSED /
  ETIMEDOUT / etc.) is included in the AI-visible message for
  debugging signal. Timeouts (DOMException TimeoutError) and HTTP 4xx/
  5xx (response received but provider-rejected) keep their existing
  OpenBBError paths — both are recoverable in different ways than
  network-unreachable.

- Tests: 6 new helpers.spec.ts cases covering TypeError("fetch failed"),
  ENOTFOUND cause preservation, generic non-network errors, HTTP
  non-2xx, and DOMException timeouts. The classifier is the security-
  critical bit (false positive => AI gives up retryable work; false
  negative => AI burns tokens), so the boundary cases are pinned.

- Re-exported NetworkUnreachableError from package index so OpenAlice-
  side code (or any future consumer) can `instanceof` against it for
  structured handling beyond just reading the message.

Verified end-to-end: simulated BLS host being unreachable through the
real tool stack (createEconomyTools → SDKEconomyClient → executor →
amakeRequest → fetch stubbed to throw TypeError("fetch failed")),
caught at the tool execute boundary as NetworkUnreachableError with
the AI-targeted message intact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@luokerenx4 luokerenx4 merged commit 36e51c6 into master May 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant