Skip to content

feat(focus): add ternary destination for FOCUS export - #39795

Open
asproul wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
TernaryInc:feature/ternary-focus-destination
Open

feat(focus): add ternary destination for FOCUS export#39795
asproul wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
TernaryInc:feature/ternary-focus-destination

Conversation

@asproul

@asproul asproul commented Sep 4, 2026

Copy link
Copy Markdown

Description

Adds a ternary FOCUS export destination (plus a ternary callback) so a LiteLLM proxy can push FOCUS-formatted spend to Ternary with just callbacks: ["ternary"] — no separate export job.

It follows the existing pure-sink destination pattern (Vantage / Mavvrik / GCS): it adds no FOCUS columns and forwards the FOCUS CSV as-is to a Ternary cost-ingestion endpoint, chunking an oversized backfill with a stable X-Ternary-Upload-Id + 0-based index/total so the receiver can stage-then-swap. A thin TernaryLogger presets the pipeline (CSV) and enriches the standard FOCUS Tags JSON with the per-request token counts the shared transformer drops (Ternary weights cost allocation by tokens). The enrichment is confined to the ternary code path and only adds keys to Tags — the shared transformer is untouched, so other FOCUS exports are unaffected.

Self-contained: no changes to existing code paths beyond the same registry/dispatch/scheduler wiring Vantage and Mavvrik already use.

New files:

  • litellm/integrations/focus/destinations/ternary_destination.py
  • litellm/integrations/ternary/ternary_logger.py
  • tests (below)

Docs land in a companion PR against BerriAI/litellm-docs: BerriAI/litellm-docs#1197.

Relevant issues

No linked issue — this mirrors the self-contained pattern of the already-merged FOCUS destinations #29935 (Mavvrik) and #29751 (GCS), which were likewise PR-only.

Type

🆕 New Feature

Testing

Mocked unit tests only — no live LLM/API calls (the HTTP handler is mocked):

  • tests/test_litellm/integrations/focus/test_ternary_destination.py — URL/auth/upload headers, single-shot vs chunked, csv-safe repartition (quoted newlines), oversized-row error, abort-on-first-chunk-failure, connection_id validation + URL-encoding.
  • tests/test_litellm/integrations/ternary/test_ternary_logger.py — config presets, cadence validation, day-aligned window, and per-row token-Tags alignment through the real FocusTransformer.
python -m pytest tests/test_litellm/integrations/ternary/ tests/test_litellm/integrations/focus/test_ternary_destination.py

43 tests pass; ruff check / ruff format and the repo type-discipline gate are clean.

Checklist

  • CLA signed
  • ruff + type-discipline clean; formatting applied
  • Unit tests added and passing (mocked; no live calls)
  • Greptile Confidence Score ≥ 4/5 (runs automatically once opened)

@codspeed-hq

codspeed-hq Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing TernaryInc:feature/ternary-focus-destination (a60a4d8) with litellm_internal_staging (df68edc)

Open in CodSpeed

Comment thread litellm/integrations/focus/destinations/ternary_destination.py
@veria-ai

veria-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a Ternary callback and FOCUS destination that enriches spend records with token-count tags and uploads CSV data to Ternary.

  • Registers and schedules the new Ternary logger.
  • Validates destination configuration and secure endpoint URLs.
  • Supports bounded, CSV-safe multipart chunking for large exports.
  • Adds mocked coverage for configuration, enrichment, scheduling, chunking, and upload behavior.

Confidence Score: 4/5

The implementation appears functionally safe, but the outstanding repository comment-policy requirement must be satisfied before merging.

The empty-content regression test now correctly fails if an HTTP client is acquired, and the prior cadence findings are fixed or withdrawn. However, asproul claimed the comment-policy finding was fixed, while several routine explanatory comments remain in tests/test_litellm/integrations/ternary/test_ternary_logger.py, so that existing rule-linked finding remains unresolved.

Files Needing Attention: tests/test_litellm/integrations/ternary/test_ternary_logger.py

Important Files Changed

Filename Overview
litellm/integrations/focus/destinations/ternary_destination.py Implements validated, chunked FOCUS CSV delivery through LiteLLM's shared asynchronous HTTP client.
litellm/integrations/ternary/ternary_logger.py Configures day-aligned Ternary exports and enriches FOCUS tags; the existing comment-policy finding remains only partly addressed.
tests/test_litellm/integrations/focus/test_ternary_destination.py Restores explicit regression coverage proving empty content does not initialize an HTTP client.
tests/test_litellm/integrations/ternary/test_ternary_logger.py Covers logger behavior comprehensively, but still contains comments covered by the outstanding repository-policy thread.

Reviews (3): Last reviewed commit: "feat(focus): add ternary destination for..." | Re-trigger Greptile

Comment thread litellm/integrations/ternary/ternary_logger.py
Comment thread litellm/integrations/ternary/ternary_logger.py Outdated
start_time: Final = (now_utc - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
return FocusTimeWindow(start_time=start_time, end_time=now_utc, frequency=self.frequency)

# -- enrichment: inject token counts the shared transformer drops --------

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Comments Violate Repository Policy

The new files add section labels and explanatory comments beyond essential complex logic. Repository policy requires removing them before merge.

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed -- stripped the section labels and explanatory prose per CLAUDE.md, keeping only lint suppressions and concise docstrings

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Stripped the remaining section labels and trimmed the restating inline comments across both test files; kept only concise business-logic rationale and rule suppressions.

@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.48980% with 38 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/integrations/ternary/ternary_logger.py 86.82% 17 Missing ⚠️
litellm/litellm_core_utils/litellm_logging.py 15.38% 11 Missing ⚠️
litellm/integrations/focus/destinations/factory.py 16.66% 10 Missing ⚠️

📢 Thoughts on this report? Let us know!

@asproul
asproul force-pushed the feature/ternary-focus-destination branch 4 times, most recently from 4229140 to ef1342f Compare September 4, 2026 21:24
@asproul
asproul requested a review from a team September 4, 2026 21:24
@asproul

asproul commented Sep 4, 2026

Copy link
Copy Markdown
Author

@greptileai

Comment thread tests/test_litellm/integrations/focus/test_ternary_destination.py Outdated
Add a `ternary` FOCUS export destination (plus a `ternary` callback) so a LiteLLM proxy can push FOCUS-formatted spend to Ternary with `callbacks: ["ternary"]` -- no separate export job

- FocusTernaryDestination: a pure sink modeled on the sibling vantage/mavvrik/gcs destinations; POSTs the FOCUS CSV to the Ternary cost-ingestion endpoint, chunking an oversized backfill (csv-module repartition, quoted-newline safe; oversized single row raises rather than silently dropping) with a stable X-Ternary-Upload-Id + 0-based index/total so the receiver stages-then-swaps. connection_id is validated + URL-encoded; base_url must be HTTPS (loopback exempt for local); the upload has a bounded timeout; it aborts on the first failed chunk so nothing partial lands
- TernaryLogger: presets the FOCUS pipeline (CSV) and enriches the FOCUS Tags JSON with the per-request token counts the shared transformer drops (Ternary weights allocation by tokens); the enrichment is confined to this path and only adds keys. Cadence is daily (default) or interval (validation loops, an explicit interval-seconds required); the export window is day-aligned and drops days before the window start so a whole-day-replace receiver never truncates
- Registration mirrors vantage/mavvrik: ternary callback literal, logging dispatch, scheduler job, and CustomLoggerRegistry entry -- no shared-code changes
- Tests: mocked unit tests only (no live calls), 100% line coverage of both new modules
@asproul
asproul force-pushed the feature/ternary-focus-destination branch from ef1342f to a60a4d8 Compare September 4, 2026 22:09
@asproul

asproul commented Sep 4, 2026

Copy link
Copy Markdown
Author

@greptileai please re-review — the two open findings (empty-content test and comment policy) are addressed in a60a4d8.

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