Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions examples/notgiven_serialization_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
"""
Example: Using Portkey with openai-agents (NotGiven Serialization)

This example demonstrates that Portkey now automatically handles NotGiven
serialization out of the box. No manual setup required!

The fix is transparent - just import and use Portkey normally.
"""

import portkey_ai
from portkey_ai import AsyncPortkey, enable_notgiven_serialization, disable_notgiven_serialization
import json


def example_automatic_serialization():
"""Example showing automatic serialization works out of the box."""
print("=== Automatic Serialization (No Setup Needed!) ===\n")

from portkey_ai._vendor.openai._types import NOT_GIVEN

# Data that contains NotGiven objects
data = {
"model": "gpt-4",
"temperature": 0.7,
"optional_param": NOT_GIVEN, # This works automatically now!
"another_param": None,
}

# Use standard json.dumps - no special encoder needed!
try:
json_string = json.dumps(data) # Just works! ✨
print(f"✅ Serialization works automatically: {json_string}\n")
except TypeError as e:
print(f"❌ Serialization failed: {e}\n")


def example_basic_usage():
"""Basic example of using PortkeyJSONEncoder for manual serialization."""
print("=== Custom Encoder (Optional) ===\n")

from portkey_ai import PortkeyJSONEncoder
from portkey_ai._vendor.openai._types import NOT_GIVEN

# You can still use the custom encoder if you prefer
data = {
"model": "gpt-4",
"temperature": 0.7,
"optional_param": NOT_GIVEN,
"another_param": None,
}

# Serialize using PortkeyJSONEncoder
try:
json_string = json.dumps(data, cls=PortkeyJSONEncoder)
print(f"✅ Successfully serialized with encoder: {json_string}\n")
except TypeError as e:
print(f"❌ Serialization failed: {e}\n")


def example_global_serialization():
"""Example showing disable/enable functionality."""
print("=== Manual Enable/Disable Example ===\n")

from portkey_ai._vendor.openai._types import NOT_GIVEN

print("Note: Serialization is already enabled automatically!")
print("But you can disable and re-enable if needed:\n")

# Disable temporarily
print("1. Disabling serialization...")
disable_notgiven_serialization()

data = {"param": NOT_GIVEN}

try:
json.dumps(data)
print(" ✅ Still works (unexpected)")
except TypeError:
print(" ✅ Correctly disabled - can't serialize NotGiven")

# Re-enable
print("\n2. Re-enabling serialization...")
enable_notgiven_serialization()

try:
json_string = json.dumps(data)
print(f" ✅ Works again: {json_string}\n")
except TypeError as e:
print(f" ❌ Failed: {e}\n")


def example_with_portkey_client():
"""Example showing how to use with a Portkey client."""
print("=== Portkey Client Example ===\n")

print("Serialization is enabled automatically - no setup needed!")

# Create Portkey client - serialization works out of the box!
client = AsyncPortkey(
api_key="your-portkey-api-key", # or set PORTKEY_API_KEY env var
virtual_key="your-virtual-key", # optional
)

print("✅ AsyncPortkey client created successfully")
print("✅ The client can be serialized by external libraries like openai-agents")

# Example: Simulate what openai-agents might do
try:
# Try to serialize the client's attributes
client_dict = {
"api_key": client.api_key,
"base_url": str(client.base_url),
"virtual_key": client.virtual_key,
}
serialized = json.dumps(client_dict)
print(f"✅ Client attributes serialized successfully: {serialized}\n")
except TypeError as e:
print(f"❌ Client serialization failed: {e}\n")


def example_before_and_after():
"""Demonstrate the problem and solution side by side."""
print("=== Before and After Comparison ===\n")

from portkey_ai._vendor.openai._types import NOT_GIVEN

data = {"param": NOT_GIVEN}

# BEFORE: This would fail
print("Before enabling global serialization:")
try:
json.dumps(data)
print("✅ Serialization succeeded (unexpected)")
except TypeError as e:
print(f"❌ Serialization failed as expected: {str(e)[:50]}...")

# AFTER: This works
print("\nAfter enabling global serialization:")
enable_notgiven_serialization()
try:
result = json.dumps(data)
print(f"✅ Serialization succeeded: {result}")
except TypeError as e:
print(f"❌ Serialization failed (unexpected): {e}")

from portkey_ai import disable_notgiven_serialization
disable_notgiven_serialization()
print()


def main():
"""Run all examples."""
print("\n" + "="*60)
print("Portkey NotGiven Serialization Examples")
print("="*60 + "\n")

print("✨ Good News: Serialization works automatically!")
print(" No manual setup required - just import and use.\n")

example_automatic_serialization()
example_with_portkey_client()
example_basic_usage()
example_global_serialization()
example_before_and_after()

print("="*60)
print("\n✨ All examples completed!")
print("\n📚 Key Takeaway: NotGiven serialization works out of the box!")
print(" For more information, see docs/notgiven-serialization.md")
print("="*60 + "\n")


if __name__ == "__main__":
main()
11 changes: 11 additions & 0 deletions portkey_ai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@
PORTKEY_PROXY_ENV,
PORTKEY_GATEWAY_URL,
)
from portkey_ai.utils.json_utils import (
PortkeyJSONEncoder,
enable_notgiven_serialization,
disable_notgiven_serialization,
)

# Automatically enable NotGiven serialization. Users can call disable_notgiven_serialization() if needed.
enable_notgiven_serialization()

api_key = os.environ.get(PORTKEY_API_KEY_ENV)
base_url = os.environ.get(PORTKEY_PROXY_ENV, PORTKEY_BASE_URL)
Expand All @@ -175,6 +183,9 @@
"LLMOptions",
"Modes",
"PortkeyResponse",
"PortkeyJSONEncoder",
"enable_notgiven_serialization",
"disable_notgiven_serialization",
"ModesLiteral",
"ProviderTypes",
"ProviderTypesLiteral",
Expand Down
12 changes: 12 additions & 0 deletions portkey_ai/_vendor/openai/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,18 @@ def __bool__(self) -> Literal[False]:
def __repr__(self) -> str:
return "NOT_GIVEN"

def __reduce__(self) -> tuple[type[NotGiven], tuple[()]]:
"""Support for pickling/serialization."""
return (self.__class__, ())

def __copy__(self) -> NotGiven:
"""Return self since NotGiven is a singleton-like sentinel."""
return self

def __deepcopy__(self, memo: dict[int, Any]) -> NotGiven:
"""Return self since NotGiven is a singleton-like sentinel."""
return self


not_given = NotGiven()
# for backwards compatibility:
Expand Down
50 changes: 46 additions & 4 deletions portkey_ai/utils/json_utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,53 @@
import json
from portkey_ai._vendor.openai._types import NotGiven


class PortkeyJSONEncoder(json.JSONEncoder):
"""Custom JSON encoder that handles Portkey-specific types like NotGiven."""

def default(self, obj):
if isinstance(obj, NotGiven):
# Return None for NotGiven instances during JSON serialization
return None
return super().default(obj)


_original_json_encoder = None


def enable_notgiven_serialization():
"""
Enable global JSON serialization support for NotGiven types.
"""
global _original_json_encoder
if _original_json_encoder is None:
_original_json_encoder = json.JSONEncoder.default

def patched_default(self, obj):
if isinstance(obj, NotGiven):
return None
return _original_json_encoder(self, obj)

json.JSONEncoder.default = patched_default


def disable_notgiven_serialization():
"""
Disable global JSON serialization support for NotGiven types.

This restores the original JSONEncoder behavior.
"""
global _original_json_encoder
if _original_json_encoder is not None:
json.JSONEncoder.default = _original_json_encoder
_original_json_encoder = None


def serialize_kwargs(**kwargs):
# Function to check if a value is serializable
def is_serializable(value):
try:
json.dumps(value)
json.dumps(value, cls=PortkeyJSONEncoder)
return True
except (TypeError, ValueError):
return False
Expand All @@ -14,14 +56,14 @@ def is_serializable(value):
serializable_kwargs = {k: v for k, v in kwargs.items() if is_serializable(v)}

# Convert to string representation
return json.dumps(serializable_kwargs)
return json.dumps(serializable_kwargs, cls=PortkeyJSONEncoder)


def serialize_args(*args):
# Function to check if a value is serializable
def is_serializable(value):
try:
json.dumps(value)
json.dumps(value, cls=PortkeyJSONEncoder)
return True
except (TypeError, ValueError):
return False
Expand All @@ -30,4 +72,4 @@ def is_serializable(value):
serializable_args = [arg for arg in args if is_serializable(arg)]

# Convert to string representation
return json.dumps(serializable_args)
return json.dumps(serializable_args, cls=PortkeyJSONEncoder)
Loading
Loading