') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); [bot] Instrument portkey-ai (830,380 weekly downloads) · Issue #696 · braintrustdata/braintrust-sdk-python · GitHub
Skip to content

[bot] Instrument portkey-ai (830,380 weekly downloads) #696

Description

@braintrust-bot
<!-- library-gap-audit: portkey-ai-py -->

Gap

portkey-ai (the Python SDK for the Portkey AI Gateway) has no Braintrust instrumentation in this repo. It provides a unified, OpenAI-SDK-compatible execution surface (Portkey(...).chat.completions.create(...), .completions.create(...), .embeddings.create(...), streaming, and tool-calling) that routes requests to 250+ upstream LLM providers via virtual keys, fallbacks, load balancing, and semantic caching.

This is directly analogous to the gateway/router libraries already instrumented in this repo — litellm and openrouter — but portkey-ai itself has no wrapper, despite having comparable or higher adoption and the same execution-oriented surface (generation, streaming, tool calls) that the existing gateway integrations cover.

Weekly downloads: 830,380 (as of 2026-08-17; source: https://pypistats.org/api/packages/portkey-ai/recent)

Braintrust docs status:not_found — checked https://www.braintrust.dev/docs/integrations; Portkey is not listed among the supported integrations, providers, or gateway/proxy tooling. The page's only guidance for unlisted providers is to use the generic AI Proxy or request an integration.

Upstream sources:

Local repo files inspected (confirming no existing instrumentation):

  • py/src/braintrust/integrations/ (recursive listing) — no portkey directory (contrast with existing gateway coverage: litellm, openrouter)
  • py/src/braintrust/wrappers/*.py — no portkey wrapper
  • py/pyproject.toml[tool.braintrust.matrix] and [tool.braintrust.vendor-packages] — no portkey/portkey-ai entry
  • py/noxfile.py — no portkey test session
  • py/src/braintrust/_generated_types.py — only false-positive substring match (...ReportKey...), not a real reference
  • Repo-wide case-insensitive grep for portkey — no genuine matches

Suggested instrumentation: wrap the Portkey client's chat.completions.create, completions.create, and embeddings.create methods (including streaming) similarly to the existing litellm/openrouter gateway wrappers, capturing the routed provider/model, request/response payloads, and token usage as span data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions