Skip to content

Fix LLM logging and cost finalization#93

Merged
INONONO66 merged 5 commits into
mainfrom
feat/llm-logging-pricing
May 28, 2026
Merged

Fix LLM logging and cost finalization#93
INONONO66 merged 5 commits into
mainfrom
feat/llm-logging-pricing

Conversation

@INONONO66
Copy link
Copy Markdown
Owner

@INONONO66 INONONO66 commented May 28, 2026

Summary

  • record request logs only for LLM generation proxy calls while forwarding non-LLM calls without rows
  • keep failed and aborted requests out of daily usage aggregation while preserving request and cost audit data
  • add fallback pricing resolution, provider cache-write defaults, and token regression guards during finalization
  • isolate mock e2e test execution and update docs for the LLM-only logging policy

Verification

  • bun run typecheck
  • bun test
  • bun run test:e2e:mock
  • bun run build
  • file-backed HTTP smoke: LLM call logged, non-LLM proxy calls forwarded without request-log rows

Summary by cubic

Limit request logging to LLM generation endpoints and harden cost finalization to keep failed/aborted requests out of daily usage while preserving audit data.

  • New Features

    • Fall back to OpenRouter model pricing when models.dev is unavailable.
    • Apply provider defaults for missing cache_write pricing (Anthropic: 1.25x input).
  • Bug Fixes

    • Only log LLM generation requests; forward non-LLM proxy calls without request-log rows.
    • Exclude failed/aborted requests from daily usage aggregation; still write cost audits.
    • Prevent token counter regressions during finalization by taking MAX values.

Written for commit 64df79b. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added OpenRouter as a fallback pricing source when the primary provider is unavailable.
  • Bug Fixes

    • Fixed token counters to only increase, preventing incorrect tracking during retries.
    • Non-LLM proxy requests now forward without creating request logs.
    • Daily usage aggregation now correctly excludes error requests from cost calculations.
  • Documentation

    • Clarified architecture documentation on request-logging behavior for LLM versus non-LLM requests.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

📝 Walkthrough

Walkthrough

This PR refines request-log initialization to skip non-LLM proxy traffic, adds resilient pricing fetching with OpenRouter fallback, prevents token-count regression during retries, and makes daily-usage updates conditional on completed request status.

Changes

Request Lifecycle & Pricing Resilience

Layer / File(s) Summary
LLM Request Classification & Non-Log Skipping
src/server/pass-through.ts, tests/e2e/proxy.test.ts, tests/unit/lifecycle.test.ts
PassThroughProxy.preLog guards non-LLM traffic with a new isLlmRequest helper that checks POST method, explicit path rules, and model presence. Non-LLM requests are forwarded without creating request-log rows. E2E tests now expect 404 for unknown routes and verify no log rows for non-LLM endpoints like /v1/models. Unit tests confirm failed LLM requests create error-status rows without daily-usage increments.
Token Counter Non-Regression & Merged Finalization
src/storage/repo.ts, src/storage/service.ts, tests/unit/storage-lifecycle-cost.test.ts, tests/unit/cost.test.ts
RequestRepo.updateFinalize uses MAX(existing, provided) for token counts to prevent regression. UsageService.finalizeUsage loads the previous request log, merges token fields with Math.max, computes cost from merged values, and conditionally updates daily usage only when lifecycle_status is "completed". Cost backfill applies daily-usage adjustments only for completed rows. Tests verify non-regression and correct daily-usage computation from merged tokens.
OpenRouter Pricing Fallback & Cache-Write Defaults
src/storage/pricing.ts, tests/unit/cost.test.ts
fetchRemotePricing() helper first attempts models.dev, then falls back to OpenRouter on failure, parsing pricing from a data array and creating multiple aliases. A new defaultCacheWritePrice() helper applies Anthropic-specific multipliers for missing cache-write cost. Unit tests verify fallback behavior when models.dev returns 503 and confirm Cost.compute uses the OpenRouter pricing correctly.

Documentation & Test Configuration

Layer / File(s) Summary
Architecture Documentation & E2E Test Script
CONTRIBUTING.md, README.md, package.json
CONTRIBUTING.md and README.md clarify that only LLM generation calls create pending request-log rows while non-LLM proxy calls are forwarded without logging. Pricing description generalized from models.dev-specific to live pricing. package.json test:e2e:mock script narrowed to run explicit E2E test files (admin-auth, body-size-limit, ready, proxy, transforms) instead of the entire tests/e2e/ directory.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 No logs for passing guests so quick,
LLM calls are where the logging sticks.
When models.dev sleeps, OpenRouter's near,
Tokens merge wisely—no counters go backward here! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and concisely summarizes the main changes: fixing LLM logging (by recording logs only for LLM calls, not non-LLM proxy calls) and cost finalization (by preventing regression of token counters, excluding failed requests from daily usage, and adding fallback pricing).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/llm-logging-pricing

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
package.json (1)

51-51: ⚡ Quick win

De-duplicate test:e2e:mock command env prefix.

Repeating the same MOCK_CLI_PROXY/CLI_PROXY_API_URL prefix for every bun test run is brittle; Bun supports passing multiple explicit test paths as positional arguments to a single bun test invocation.

♻️ Proposed simplification
-    "test:e2e:mock": "MOCK_CLI_PROXY=1 CLI_PROXY_API_URL=http://localhost:18317 bun test tests/e2e/admin-auth.test.ts && MOCK_CLI_PROXY=1 CLI_PROXY_API_URL=http://localhost:18317 bun test tests/e2e/body-size-limit.test.ts && MOCK_CLI_PROXY=1 CLI_PROXY_API_URL=http://localhost:18317 bun test tests/e2e/ready.test.ts && MOCK_CLI_PROXY=1 CLI_PROXY_API_URL=http://localhost:18317 bun test tests/e2e/proxy.test.ts && MOCK_CLI_PROXY=1 CLI_PROXY_API_URL=http://localhost:18317 bun test tests/e2e/transforms.test.ts",
+    "test:e2e:mock": "MOCK_CLI_PROXY=1 CLI_PROXY_API_URL=http://localhost:18317 bun test tests/e2e/admin-auth.test.ts tests/e2e/body-size-limit.test.ts tests/e2e/ready.test.ts tests/e2e/proxy.test.ts tests/e2e/transforms.test.ts",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 51, The npm script "test:e2e:mock" repeats the
environment prefix for each bun test invocation; refactor it to set
MOCK_CLI_PROXY and CLI_PROXY_API_URL once and call bun test with all test file
paths as positional arguments (e.g., bun test tests/e2e/admin-auth.test.ts
tests/e2e/body-size-limit.test.ts ...), so update the "test:e2e:mock" entry in
package.json to a single env prefix followed by one bun test command listing all
the e2e test paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/storage/pricing.ts`:
- Around line 214-219: The current success path treats a 200 JSON response with
zero usable pricing entries as valid and caches empty data; change the logic
after parsing the response (the call to res.json() and buildPricingMap) to
detect if buildPricingMap(raw) returns an empty map (no entries) and, in that
case, treat it as a failure by logging a warning via logger.warn and calling the
fallback fetchOpenRouterPricing() (or throwing to trigger the existing catch)
instead of returning the empty map; apply the same check and behavior
modification to the analogous code block around the buildPricingMap usage at the
other location (lines 235–250) so empty payloads from remote providers fall back
to OpenRouter rather than being cached as fresh data.

---

Nitpick comments:
In `@package.json`:
- Line 51: The npm script "test:e2e:mock" repeats the environment prefix for
each bun test invocation; refactor it to set MOCK_CLI_PROXY and
CLI_PROXY_API_URL once and call bun test with all test file paths as positional
arguments (e.g., bun test tests/e2e/admin-auth.test.ts
tests/e2e/body-size-limit.test.ts ...), so update the "test:e2e:mock" entry in
package.json to a single env prefix followed by one bun test command listing all
the e2e test paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dbd6237f-1840-4900-9f26-c32a5c95b098

📥 Commits

Reviewing files that changed from the base of the PR and between ffd8a44 and 64df79b.

📒 Files selected for processing (11)
  • CONTRIBUTING.md
  • README.md
  • package.json
  • src/server/pass-through.ts
  • src/storage/pricing.ts
  • src/storage/repo.ts
  • src/storage/service.ts
  • tests/e2e/proxy.test.ts
  • tests/unit/cost.test.ts
  • tests/unit/lifecycle.test.ts
  • tests/unit/storage-lifecycle-cost.test.ts

Comment thread src/storage/pricing.ts
Comment on lines +214 to +219
const raw = await res.json() as Record<string, ModelsDevProvider>;
return buildPricingMap(raw);
} catch (err) {
logger.warn("models.dev pricing fetch failed, trying OpenRouter", { err, source: "models.dev" });
return fetchOpenRouterPricing();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail refresh when remote payload parses to zero usable pricing entries.

Right now, an HTTP 200 with no usable models is treated as success and cached as fresh data. That can suppress pricing resolution for a full TTL window.

💡 Suggested fix
   async function fetchRemotePricing(): Promise<PricingMap> {
     try {
       const res = await fetch(MODELS_DEV_URL, { signal: AbortSignal.timeout(30_000) });
       if (!res.ok) throw new Error(`models.dev returned HTTP ${res.status}`);
       const raw = await res.json() as Record<string, ModelsDevProvider>;
-      return buildPricingMap(raw);
+      const map = buildPricingMap(raw);
+      if (map.size === 0) throw new Error("models.dev returned no usable pricing entries");
+      return map;
     } catch (err) {
       logger.warn("models.dev pricing fetch failed, trying OpenRouter", { err, source: "models.dev" });
       return fetchOpenRouterPricing();
     }
   }
@@
   async function fetchOpenRouterPricing(): Promise<PricingMap> {
     const res = await fetch(OPENROUTER_MODELS_URL, { signal: AbortSignal.timeout(30_000) });
     if (!res.ok) throw new Error(`OpenRouter returned HTTP ${res.status}`);
     const raw = await res.json() as { data?: OpenRouterModel[] };
     const map: PricingMap = new Map();
@@
-    logger.info("loaded pricing aliases", { aliases: map.size, source: "openrouter" });
+    if (map.size === 0) throw new Error("OpenRouter returned no usable pricing entries");
+    logger.info("loaded pricing aliases", { aliases: map.size, source: "openrouter" });
     return map;
   }

Also applies to: 235-250

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/storage/pricing.ts` around lines 214 - 219, The current success path
treats a 200 JSON response with zero usable pricing entries as valid and caches
empty data; change the logic after parsing the response (the call to res.json()
and buildPricingMap) to detect if buildPricingMap(raw) returns an empty map (no
entries) and, in that case, treat it as a failure by logging a warning via
logger.warn and calling the fallback fetchOpenRouterPricing() (or throwing to
trigger the existing catch) instead of returning the empty map; apply the same
check and behavior modification to the analogous code block around the
buildPricingMap usage at the other location (lines 235–250) so empty payloads
from remote providers fall back to OpenRouter rather than being cached as fresh
data.

@INONONO66 INONONO66 merged commit 7102acc into main May 28, 2026
3 checks passed
@INONONO66 INONONO66 deleted the feat/llm-logging-pricing branch May 28, 2026 17:49
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