@@ -12,7 +12,7 @@ portkey-python-sdk/
1212│ ├── __init__.py # Public API - exports all classes
1313│ ├── version.py # SDK version
1414│ ├── _vendor/ # Vendored dependencies
15- │ │ └── openai/ # Vendored OpenAI SDK (e.g., v2.7.1 )
15+ │ │ └── openai/ # Vendored OpenAI SDK (currently v2.16.0 )
1616│ ├── api_resources/ # Core SDK implementation
1717│ │ ├── __init__.py # Internal exports
1818│ │ ├── client.py # Portkey & AsyncPortkey clients
@@ -41,7 +41,7 @@ The SDK vendors the OpenAI Python SDK to avoid version conflicts:
4141``` toml
4242# vendorize.toml
4343target = " portkey_ai/_vendor"
44- packages = [" openai==2.7.1 " ]
44+ packages = [" openai==2.16.0 " ] # Update this version as needed
4545```
4646
4747### Key Vendoring Quirks
@@ -407,19 +407,196 @@ portkey_ai/__init__.py # Final public export + add to __all__
407407
408408## Updating Vendored OpenAI SDK
409409
410- 1 . Update version in ` vendorize.toml ` :
411- ``` toml
412- packages = [" openai==X.Y.Z" ]
410+ ### Step-by-Step Process
411+
412+ #### Step 1: Compare SDK Versions
413+ Before vendoring, compare changes between versions:
414+ ```
415+ https://github.com/openai/openai-python/compare/vOLD...vNEW
416+ ```
417+ This helps identify:
418+ - New methods/resources added
419+ - Removed/deprecated methods
420+ - Signature changes in existing methods
421+
422+ #### Step 2: Clean and Re-vendor
423+
424+ ``` bash
425+ # Delete the old vendor folder contents
426+ rm -rf portkey_ai/_vendor/*
427+
428+ # Update version in vendorize.toml
429+ packages = [" openai==X.Y.Z" ]
430+
431+ # Run vendorize (install with: pip install vendorize)
432+ python-vendorize
433+ ```
434+
435+ The tool rewrites imports from ` openai.* ` to ` portkey_ai._vendor.openai.* `
436+
437+ #### Step 3: Apply Portkey Customizations
438+
439+ ** Two files MUST be modified after every vendoring:**
440+
441+ 1 . ** ` portkey_ai/_vendor/openai/_constants.py ` **
442+ ``` python
443+ # Change DEFAULT_MAX_RETRIES from 2 to 1
444+ DEFAULT_MAX_RETRIES = 1
445+ ```
446+
447+ 2 . ** ` portkey_ai/_vendor/openai/_base_client.py ` **
448+
449+ Replace the ` _should_retry ` method with Portkey's custom logic:
450+ ``` python
451+ def _should_retry (self , response : httpx.Response) -> bool :
452+ # Custom Retry Conditions
453+ retry_status_code = response.status_code
454+ retry_trace_id = response.headers.get(" x-portkey-trace-id" )
455+ retry_request_id = response.headers.get(" x-portkey-request-id" )
456+ retry_gateway_exception = response.headers.get(" x-portkey-gateway-exception" )
457+
458+ if (
459+ retry_status_code < 500
460+ or retry_trace_id
461+ or retry_request_id
462+ or retry_gateway_exception
463+ ):
464+ return False
465+
466+ return True
413467 ```
414468
415- 2 . Run vendorize tool (typically ` vendorize ` or ` python -m vendorize ` )
469+ #### Step 4: Run Lint to Find Issues
470+
471+ ``` bash
472+ make lint
473+ ```
474+
475+ This will surface:
476+ - Type incompatibilities
477+ - Missing imports
478+ - Signature mismatches
479+
480+ #### Step 5: Update Portkey Wrappers
481+
482+ For each wrapper that needs updating:
483+
484+ ** Adding new parameters:**
485+ ``` python
486+ # If wrapper explicitly lists parameters (not just **kwargs)
487+ # Add new params with Union[Type, Omit] = omit pattern
488+ def create (
489+ self ,
490+ * ,
491+ existing_param : Union[str , Omit] = omit,
492+ new_param : Union[str , Omit] = omit, # Add new param
493+ ** kwargs ,
494+ ):
495+ response = self .openai_client.with_raw_response.resource.create(
496+ existing_param = existing_param,
497+ new_param = new_param, # Pass to underlying client
498+ ...
499+ )
500+ ```
501+
502+ ** Adding new methods:**
503+ ``` python
504+ def new_method (
505+ self ,
506+ * ,
507+ param : Union[str , Omit] = omit,
508+ ** kwargs ,
509+ ) -> NewResponseType:
510+ import json
511+ extra_headers = kwargs.pop(" extra_headers" , None )
512+ extra_query = kwargs.pop(" extra_query" , None )
513+ extra_body = kwargs.pop(" extra_body" , None )
514+ timeout = kwargs.pop(" timeout" , None )
515+
516+ response = self .openai_client.with_raw_response.resource.new_method(
517+ param = param,
518+ extra_headers = extra_headers,
519+ extra_query = extra_query,
520+ extra_body = {** (extra_body or {}), ** kwargs},
521+ timeout = timeout,
522+ )
523+ data = NewResponseType(** json.loads(response.text))
524+ data._headers = response.headers
525+ return data
526+ ```
527+
528+ ** Adding new response types:**
529+ ``` python
530+ # In api_resources/types/
531+ class NewResponseType (BaseModel , extra = " allow" ):
532+ id : str
533+ created_at: int
534+ # ... other fields from OpenAI type
535+ _headers: Optional[httpx.Headers] = PrivateAttr()
536+
537+ def get_headers (self ) -> Optional[Dict[str , str ]]:
538+ return parse_headers(self ._headers)
539+ ```
540+
541+ #### Step 6: Handle Type Ignore Comments
542+
543+ Sometimes Portkey uses more flexible types than OpenAI's strict literals:
544+ ``` python
545+ # Portkey allows any string, OpenAI expects specific literals
546+ response = self .openai_client.with_raw_response.resource.method(
547+ model = model, # type: ignore [ arg -type ]
548+ ...
549+ )
550+ ```
551+
552+ #### Step 7: Verify and Test
553+
554+ ``` bash
555+ # Format code
556+ make format
557+
558+ # Run all checks
559+ make lint
560+
561+ # Test imports
562+ python -c " from portkey_ai import Portkey; print('OK')"
416563
417- 3 . The tool rewrites imports from ` openai.* ` to ` portkey_ai._vendor.openai.* `
564+ # Run tests (requires valid virtual keys)
565+ pytest . -n 10
566+ ```
567+
568+ ### Wrapper Resilience Pattern
569+
570+ Most Portkey wrappers use ` **kwargs ` pattern which automatically forwards new parameters:
571+
572+ ``` python
573+ def create (self , * , name : str , ** kwargs ):
574+ extra_headers = kwargs.pop(" extra_headers" , None )
575+ # ... other pops
576+ response = self .openai_client.with_raw_response.resource.create(
577+ name = name,
578+ extra_headers = extra_headers,
579+ extra_body = {** (extra_body or {}), ** kwargs}, # Unknown params go here
580+ )
581+ ```
418582
419- 4 . After vendoring:
420- - Check for new OpenAI resources that need Portkey wrappers
421- - Update any Portkey types that mirror OpenAI types
422- - Test thoroughly - OpenAI SDK changes can break wrappers
583+ This means many new OpenAI parameters work automatically without code changes. Only add explicit parameters when:
584+ 1 . The wrapper doesn't pass ` **kwargs ` to ` extra_body `
585+ 2 . You want IDE autocomplete/type hints for important params
586+ 3 . The parameter needs special handling
587+
588+ ### Checklist for Vendoring
589+
590+ - [ ] Delete ` portkey_ai/_vendor/* `
591+ - [ ] Update ` vendorize.toml ` with new version
592+ - [ ] Run ` python-vendorize `
593+ - [ ] Set ` DEFAULT_MAX_RETRIES = 1 ` in ` _constants.py `
594+ - [ ] Replace ` _should_retry ` in ` _base_client.py `
595+ - [ ] Run ` make lint ` and fix errors
596+ - [ ] Add new methods/types if needed
597+ - [ ] Run ` make format `
598+ - [ ] Test imports work
599+ - [ ] Commit vendored code first, then wrapper changes
423600
424601## Common Gotchas
425602
0 commit comments