Skip to content

Add Ramp as a built-in success callback - #23769

Merged
krrish-berri-2 merged 1 commit into
BerriAI:mainfrom
kedarthakkar:ramp-integration
Apr 9, 2026
Merged

Add Ramp as a built-in success callback#23769
krrish-berri-2 merged 1 commit into
BerriAI:mainfrom
kedarthakkar:ramp-integration

Conversation

@kedarthakkar

Copy link
Copy Markdown
Contributor

Summary

  • Registers "ramp" as a built-in GenericAPILogger callback so customers can enable AI usage reporting to Ramp with just:
    litellm.success_callback = ["ramp"]
  • Only requires the RAMP_API_KEY environment variable — the endpoint (https://api.ramp.com/developer/v1/ai-usage) is hardcoded.

Changes

  1. litellm/__init__.py — Add "ramp" to _custom_logger_compatible_callbacks_literal
  2. generic_api_compatible_callbacks.json — Add Ramp callback config (endpoint, headers, env vars)
  3. custom_logger_registry.py — Register "ramp": GenericAPILogger class mapping
  4. litellm_logging.py — Add instantiation in the callback init chain

Test plan

  • Set RAMP_API_KEY env var and configure litellm.success_callback = ["ramp"]
  • Make a completion call and verify the callback fires and POSTs to the Ramp endpoint
  • Verify no regression with existing generic API callbacks (e.g. rubrik, sumologic)

🤖 Generated with Claude Code

@CLAassistant

CLAassistant commented Mar 16, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@vercel

vercel Bot commented Mar 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Apr 8, 2026 11:51pm

Request Review

@codspeed-hq

codspeed-hq Bot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing kedarthakkar:ramp-integration (7540be3) with main (072d410)

Open in CodSpeed

@greptile-apps

greptile-apps Bot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds \"ramp\" as a new built-in GenericAPILogger callback by adding an entry to generic_api_compatible_callbacks.json (hardcoded endpoint, RAMP_API_KEY-based auth) and adding documentation. The approach is correct — the existing is_callback_compatible check in logging_callback_manager.py automatically picks up new JSON entries without needing changes to __init__.py, custom_logger_registry.py, or litellm_logging.py (contrary to the PR description). The implementation is minimal and sound.

Confidence Score: 5/5

Safe to merge — the change is a self-contained JSON entry and documentation addition with no risk to existing callbacks.

The two changed files are additive only: a new key in the JSON config and a new doc page. The existing is_callback_compatible + _generic_api_logger_cache path in logging_callback_manager.py correctly handles new JSON entries without any additional wiring. All remaining findings are P2 (a duplicate snippet in docs). No regressions are possible from these changes.

No files require special attention.

Vulnerabilities

No security concerns identified. The RAMP_API_KEY is read from an environment variable (not hardcoded), and the endpoint is a static HTTPS URL owned by Ramp. No auth-layer changes were made.

Important Files Changed

Filename Overview
litellm/integrations/generic_api/generic_api_compatible_callbacks.json Adds the ramp entry with hardcoded endpoint, bearer auth via RAMP_API_KEY, and success-only event filtering — consistent with existing entries like rubrik and qualifire_eval.
docs/my-website/docs/observability/ramp_integration.md New documentation page for Ramp integration — clear and accurate, but contains a redundant standalone litellm.callbacks = ["ramp"] snippet before the full example that already includes it.

Sequence Diagram

sequenceDiagram
    participant User
    participant LiteLLM
    participant LoggingCallbackManager
    participant GenericAPICallback as generic_api_callback.py
    participant RampAPI as Ramp API

    User->>LiteLLM: litellm.callbacks = ["ramp"]
    LiteLLM->>LoggingCallbackManager: _safe_add_callback_to_list("ramp")
    LoggingCallbackManager->>LoggingCallbackManager: _add_custom_callback_generic_api_str("ramp")
    LoggingCallbackManager->>GenericAPICallback: is_callback_compatible("ramp")
    GenericAPICallback-->>LoggingCallbackManager: True (found in JSON)
    LoggingCallbackManager->>GenericAPICallback: GenericAPILogger(callback_name="ramp")
    GenericAPICallback->>GenericAPICallback: Load endpoint + headers from JSON, substitute RAMP_API_KEY env var
    GenericAPICallback-->>LoggingCallbackManager: GenericAPILogger instance
    LoggingCallbackManager->>LoggingCallbackManager: Cache as _generic_api_logger_cache["ramp"]

    User->>LiteLLM: litellm.completion(...)
    LiteLLM->>GenericAPICallback: async_log_success_event(StandardLoggingPayload)
    GenericAPICallback->>RampAPI: POST https://api.ramp.com/developer/v1/ai-usage/litellm
    RampAPI-->>GenericAPICallback: 200 OK
Loading

Reviews (12): Last reviewed commit: "Add Ramp as a built-in generic API callb..." | Re-trigger Greptile

Comment thread litellm/litellm_core_utils/litellm_logging.py Outdated
Comment thread litellm/litellm_core_utils/custom_logger_registry.py Outdated
Comment thread litellm/litellm_core_utils/custom_logger_registry.py Outdated
@kedarthakkar

Copy link
Copy Markdown
Contributor Author

@krrishdholakia @Sameerlite Any chance either of you could take a look? Hoping to get this out as fast as possible 🙏

@kedarthakkar

Copy link
Copy Markdown
Contributor Author

@ghost

ghost commented Mar 20, 2026

Copy link
Copy Markdown

@kedarthakkar why not just register it like this - https://docs.litellm.ai/docs/contribute_integration/custom_webhook_api?

you don't need to add so much code

just add to

"endpoint": "{{environment_variables.SAMPLE_CALLBACK_URL}}",

@kedarthakkar

Copy link
Copy Markdown
Contributor Author

@kedarthakkar why not just register it like this - https://docs.litellm.ai/docs/contribute_integration/custom_webhook_api?

you don't need to add so much code

just add to

"endpoint": "{{environment_variables.SAMPLE_CALLBACK_URL}}",

Thanks @krrishdholakia this is way better! Just pushed the changes.

@kedarthakkar

Copy link
Copy Markdown
Contributor Author

Actually seeing an issue with the new approach: for non-enterprise LiteLLM, we see this silently fail as there's no named ramp callback in the _init_custom_logger_compatible_class chain and GenericAPILogger is stubbed with CustomLogger.

@ghost

ghost commented Mar 20, 2026

Copy link
Copy Markdown

how do i repro @kedarthakkar

@kedarthakkar

Copy link
Copy Markdown
Contributor Author

how do i repro @kedarthakkar

@krrishdholakia To make it easy to repro I put together kedarthakkar#2 and kedarthakkar#3 to replace the Ramp endpoint with a public webhook endpoint. Then run the test below:

# First, install litellm with all dependencies:
pip install litellm

# Then overlay the fork branch (--no-deps is safe now that deps are installed):
pip install --force-reinstall --no-deps git+https://github.com/<your-fork>/litellm.git@ramp-json-config-only
# or
pip install --force-reinstall --no-deps git+https://github.com/<your-fork>/litellm.git@ramp-full-integration

RAMP_API_KEY=test-key OPENAI_API_KEY=sk-... python -c "
import asyncio, litellm
litellm.success_callback = ['ramp']
async def test():
    r = await litellm.acompletion(model='openai/gpt-4o-mini', messages=[{'role':'user','content':'hi'}], max_tokens=3)
    print(r.choices[0].message.content)
    await asyncio.sleep(15)
asyncio.run(test())
"

For the first branch (doesn't work) I get:

curl -s 'https://webhook.site/token/d789fafe-f4f6-4faa-b01a-15dc0dade5fc/requests?sorting=newest' | python3 -c "
import json, sys
data = json.load(sys.stdin)
reqs = data.get('data', [])
print(f'Requests received: {len(reqs)}')
if reqs:
    for i, req in enumerate(reqs):
        print(f'--- Request {i+1} ---')
        content = req.get('content', '')
        print(f'  Raw content (first 200 chars): {content[:200]}')
        try:
            parsed = json.loads(content)
            if isinstance(parsed, list) and parsed:
                e = parsed[0]
                print(f'  model: {e.get(\"model\")}')
                print(f'  id: {e.get(\"id\")}')
                print(f'  response_cost: {e.get(\"response_cost\")}')
        except:
            pass
else:
    print('No requests received - callback did NOT fire (as expected for Branch 1)')
"
Requests received: 0
No requests received - callback did NOT fire (as expected for Branch 1)

And for the second branch (works) I get:

curl -s 'https://webhook.site/token/d789fafe-f4f6-4faa-b01a-15dc0dade5fc/requests?sorting=newest' | python3 -c "
import json, sys
data = json.load(sys.stdin)
reqs = data.get('data', [])
print(f'Requests received: {len(reqs)}')
if reqs:
    for i, req in enumerate(reqs):
        print(f'--- Request {i+1} ---')
        content = req.get('content', '')
        print(f'  Method: {req.get(\"method\")}')
        print(f'  URL: {req.get(\"url\")}')
        print(f'  Created at: {req.get(\"created_at\")}')
        try:
            parsed = json.loads(content)
            if isinstance(parsed, list) and parsed:
                e = parsed[0]
                print(f'  model: {e.get(\"model\")}')
                print(f'  id: {e.get(\"id\")}')
                print(f'  response_cost: {e.get(\"response_cost\")}')
                print(f'  prompt_tokens: {e.get(\"prompt_tokens\")}')
                print(f'  completion_tokens: {e.get(\"completion_tokens\")}')
                print(f'  total_tokens: {e.get(\"total_tokens\")}')
                print(f'  startTime: {e.get(\"startTime\")}')
                print(f'  endTime: {e.get(\"endTime\")}')
            elif isinstance(parsed, dict):
                print(f'  Payload (dict): {json.dumps(parsed, indent=2)[:500]}')
            else:
                print(f'  Payload type: {type(parsed).__name__}')
        except:
            print(f'  Raw content (first 500 chars): {content[:500]}')
else:
    print('No requests received - callback did NOT fire')
"
Requests received: 1
--- Request 1 ---
  Method: POST
  URL: https://webhook.site/d789fafe-f4f6-4faa-b01a-15dc0dade5fc
  Created at: 2026-03-20 17:04:40
  model: gpt-4o-mini
  id: chatcmpl-DLXYxUbrxITWVS9iJAN739VH2kpIu
  response_cost: 3e-06
  prompt_tokens: 8
  completion_tokens: 3
  total_tokens: 11
  startTime: 1774026275.083472
  endTime: 1774026275.933429

@kedarthakkar

Copy link
Copy Markdown
Contributor Author

@krrish-berri-2 could you take a look at the example above when you get a chance?

@@ -33,5 +33,14 @@
"X-Qualifire-API-Key": "{{environment_variables.QUALIFIRE_API_KEY}}"
},
"environment_variables": ["QUALIFIRE_API_KEY", "QUALIFIRE_WEBHOOK_URL"]
},
"ramp": {

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.

can you add docs for this @kedarthakkar

@krrish-berri-2
krrish-berri-2 enabled auto-merge (squash) April 8, 2026 22:21
@krrish-berri-2
krrish-berri-2 disabled auto-merge April 8, 2026 22:21
@krrish-berri-2

Copy link
Copy Markdown
Contributor
Screenshot 2026-04-08 at 3 22 00 PM

@kedarthakkar can you fix this please?

@greptile-apps

greptile-apps Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Tip:

Greploop — Automatically fix all review issues by running /greploops in Claude Code. It iterates: fix, push, re-review, repeat until 5/5 confidence.

Use the Greptile plugin for Claude Code to query reviews, search comments, and manage custom context directly from your terminal.

@codecov

codecov Bot commented Apr 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@kedarthakkar

Copy link
Copy Markdown
Contributor Author
Screenshot 2026-04-08 at 3 22 00 PM @kedarthakkar can you fix this please?

@krrish-berri-2 Yeah will do. Also did you have a chance to take a look at the example above? I think we still see:

Actually seeing an issue with the new approach: for non-enterprise LiteLLM, we see this silently fail as there's no named ramp callback in the _init_custom_logger_compatible_class chain and GenericAPILogger is stubbed with CustomLogger.

@krrish-berri-2
krrish-berri-2 merged commit 233870d into BerriAI:main Apr 9, 2026
49 of 51 checks passed
yuneng-berri added a commit that referenced this pull request Apr 14, 2026
- Add 8 content PRs that merged directly to the release branch outside the listed staging PRs: #23769 (Ramp callback), #25252 (JWT OAuth2 override), #25254 (AWS GovCloud mode), #25258 (batch-limit cleanup), #25334 (router custom_llm_provider), #25345 (Triton embeddings), #25347 (tag-based routing), #25358 (Baseten pricing attribution)
- Add @kedarthakkar to new contributors (first-ever PR via #23769)
- Update RELEASE_NOTES_GENERATION_INSTRUCTIONS: require walking git log range between release tags in addition to staging PRs, and verify new-contributor status per author rather than trusting the GH release body floor
ishaan-berri pushed a commit that referenced this pull request Apr 15, 2026
- Add 8 content PRs that merged directly to the release branch outside the listed staging PRs: #23769 (Ramp callback), #25252 (JWT OAuth2 override), #25254 (AWS GovCloud mode), #25258 (batch-limit cleanup), #25334 (router custom_llm_provider), #25345 (Triton embeddings), #25347 (tag-based routing), #25358 (Baseten pricing attribution)
- Add @kedarthakkar to new contributors (first-ever PR via #23769)
- Update RELEASE_NOTES_GENERATION_INSTRUCTIONS: require walking git log range between release tags in addition to staging PRs, and verify new-contributor status per author rather than trusting the GH release body floor
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
- Add 8 content PRs that merged directly to the release branch outside the listed staging PRs: BerriAI#23769 (Ramp callback), BerriAI#25252 (JWT OAuth2 override), BerriAI#25254 (AWS GovCloud mode), BerriAI#25258 (batch-limit cleanup), BerriAI#25334 (router custom_llm_provider), BerriAI#25345 (Triton embeddings), BerriAI#25347 (tag-based routing), BerriAI#25358 (Baseten pricing attribution)
- Add @kedarthakkar to new contributors (first-ever PR via BerriAI#23769)
- Update RELEASE_NOTES_GENERATION_INSTRUCTIONS: require walking git log range between release tags in addition to staging PRs, and verify new-contributor status per author rather than trusting the GH release body floor
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
- Add 8 content PRs that merged directly to the release branch outside the listed staging PRs: BerriAI#23769 (Ramp callback), BerriAI#25252 (JWT OAuth2 override), BerriAI#25254 (AWS GovCloud mode), BerriAI#25258 (batch-limit cleanup), BerriAI#25334 (router custom_llm_provider), BerriAI#25345 (Triton embeddings), BerriAI#25347 (tag-based routing), BerriAI#25358 (Baseten pricing attribution)
- Add @kedarthakkar to new contributors (first-ever PR via BerriAI#23769)
- Update RELEASE_NOTES_GENERATION_INSTRUCTIONS: require walking git log range between release tags in addition to staging PRs, and verify new-contributor status per author rather than trusting the GH release body floor
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.

3 participants