diff --git a/CHANGELOG.md b/CHANGELOG.md index e5b5428..2588247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,34 @@ Notable additions, fixes, or breaking changes to the Freeplay SDK. ### Added +- **Gemini API Provider Support**: Added support for Google's Gemini API as a new provider option (`provider="gemini"`), complementing the existing Vertex AI provider. This provides a simpler authentication method using API keys instead of GCP service accounts. + + ```python + # Vertex AI (existing - uses GCP authentication) + client.recordings.create( + RecordPayload( + project_id=project_id, + all_messages=[...], + call_info=CallInfo(provider="vertex", model="gemini-1.5-pro") + ) + ) + + # Gemini API (new - uses simple API key authentication) + client.recordings.create( + RecordPayload( + project_id=project_id, + all_messages=[...], + call_info=CallInfo(provider="gemini", model="gemini-2.0-flash") + ) + ) + ``` + + **Note**: Both providers remain fully supported. Vertex AI is recommended for GCP-integrated environments, while Gemini API provides a simpler setup for standalone applications. + - Interactive REPL for development and testing: - `make repl` - Production mode (connects to app.freeplay.ai with SSL verification enabled) - `make repl-local` - Local development mode (connects to localhost:8000 with SSL verification disabled) + - `make repl ARGS="--local"` - Alternative syntax for local mode - Pre-loaded imports (Freeplay client, etc.) - Environment variables automatically loaded from `.env` file - Pre-initialized `client` variable ready to use @@ -43,13 +68,13 @@ Notable additions, fixes, or breaking changes to the Freeplay SDK. } ] - # Use in recordings + # Use in recordings - works with both Vertex and Gemini providers client.recordings.create( RecordPayload( project_id=project_id, all_messages=[...], tool_schema=tool_schema, - call_info=CallInfo(provider="vertex", model="gemini-2.0-flash") + call_info=CallInfo(provider="vertex", model="gemini-1.5-pro") # or provider="gemini" ) ) ``` diff --git a/Makefile b/Makefile index 3ca1cdf..59f36f4 100644 --- a/Makefile +++ b/Makefile @@ -32,9 +32,10 @@ run-%: # Start interactive REPL with Freeplay client initialized # By default connects to production (app.freeplay.ai) # Use 'make repl-local' for local development with SSL bypass +# Or pass arguments: make repl ARGS="--local" .PHONY: repl repl: - set -a; source .env 2>/dev/null || true; set +a; uv run python -i scripts/repl_setup.py + set -a; source .env 2>/dev/null || true; set +a; uv run python -i scripts/repl_setup.py $(ARGS) # Start REPL in local mode (connects to localhost, disables SSL verification) .PHONY: repl-local diff --git a/README.md b/README.md index 826685d..f5715c1 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,37 @@ fp_client.recordings.create( See the [SDK Setup guide](https://docs.freeplay.ai/freeplay-sdk/setup) for complete examples. +## Supported Providers + +Freeplay supports multiple LLM providers for recording and observability: + +- **OpenAI** (`provider="openai"`) +- **Anthropic** (`provider="anthropic"`) +- **Azure OpenAI** (`provider="azure"`) +- **AWS Bedrock** (`provider="bedrock"`) +- **Google Vertex AI** (`provider="vertex"`) - Google's cloud AI platform +- **Google Gemini API** (`provider="gemini"`) - Google's API with simple key auth ⭐ **NEW** + +### Gemini API vs Vertex AI + +Both providers support Google's Gemini models but differ in setup: + +| Feature | Vertex AI | Gemini API | +|---------|-----------|------------| +| **Authentication** | GCP Service Account | API Key | +| **Setup Complexity** | High (GCP infrastructure) | Low (just API key) | +| **Use Case** | Enterprise, GCP-integrated | Standalone applications | + +```python +# Vertex AI (enterprise, GCP) +CallInfo(provider="vertex", model="gemini-1.5-pro") + +# Gemini API (simple setup) +CallInfo(provider="gemini", model="gemini-2.0-flash") +``` + +See [`examples/gemini_api_example.py`](https://github.com/freeplayai/freeplay-python/blob/main/examples/gemini_api_example.py) for detailed examples. + ## Configuration ### Environment variables diff --git a/examples/gemini_api_example.py b/examples/gemini_api_example.py new file mode 100644 index 0000000..c721732 --- /dev/null +++ b/examples/gemini_api_example.py @@ -0,0 +1,133 @@ +""" +Example: Using the Gemini API Provider + +This example demonstrates how to use Google's Gemini API provider with Freeplay. +The Gemini API provider uses simple API key authentication, compared to Vertex AI +which requires GCP service account authentication. + +Both providers support the same Gemini models but differ in: +- Authentication method (API key vs. GCP credentials) +- Endpoint (generativelanguage.googleapis.com vs. aiplatform.googleapis.com) +- Setup complexity (simple vs. GCP infrastructure) + +Prerequisites: +- Set FREEPLAY_API_KEY in your environment +- Set FREEPLAY_PROJECT_ID in your environment +- Gemini API configured in Freeplay UI with API key +""" + +import os +from freeplay import Freeplay, RecordPayload, CallInfo + +# Initialize Freeplay client +client = Freeplay( + freeplay_api_key=os.environ["FREEPLAY_API_KEY"], + api_base="https://app.freeplay.ai/api", +) + +project_id = os.environ["FREEPLAY_PROJECT_ID"] + +# Example 1: Basic recording with Gemini API +print("Example 1: Basic Gemini API recording") +response = client.recordings.create( + RecordPayload( + project_id=project_id, + all_messages=[ + {"role": "user", "content": "What is the capital of France?"}, + {"role": "assistant", "content": "The capital of France is Paris."}, + ], + call_info=CallInfo( + provider="gemini", # Use Gemini API provider + model="gemini-2.0-flash", + ), + ) +) +print(f"✅ Recorded with Gemini API: {response.completion_id}") + +# Example 2: Gemini API with tool schema +print("\nExample 2: Gemini API with tool schema") + +tool_schema = [ + { + "functionDeclarations": [ + { + "name": "get_weather", + "description": "Get the current weather for a location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "City name, e.g., 'San Francisco'", + }, + "units": { + "type": "string", + "enum": ["celsius", "fahrenheit"], + "description": "Temperature units", + }, + }, + "required": ["location"], + }, + } + ] + } +] + +response = client.recordings.create( + RecordPayload( + project_id=project_id, + all_messages=[ + {"role": "user", "content": "What's the weather in Tokyo?"}, + {"role": "assistant", "content": "Let me check the weather for you."}, + ], + tool_schema=tool_schema, + call_info=CallInfo( + provider="gemini", # Use Gemini API provider + model="gemini-2.5-pro", + ), + ) +) +print(f"✅ Recorded with tool schema: {response.completion_id}") + +# Example 3: Comparison - Vertex AI vs Gemini API +print("\nExample 3: Provider comparison") + +# Vertex AI (GCP authentication) +vertex_response = client.recordings.create( + RecordPayload( + project_id=project_id, + all_messages=[ + {"role": "user", "content": "Hello from Vertex AI"}, + {"role": "assistant", "content": "Hello! I'm using Vertex AI."}, + ], + call_info=CallInfo( + provider="vertex", # Vertex AI provider + model="gemini-1.5-pro", + ), + ) +) +print(f"✅ Vertex AI: {vertex_response.completion_id}") + +# Gemini API (simple API key) +gemini_response = client.recordings.create( + RecordPayload( + project_id=project_id, + all_messages=[ + {"role": "user", "content": "Hello from Gemini API"}, + {"role": "assistant", "content": "Hello! I'm using Gemini API."}, + ], + call_info=CallInfo( + provider="gemini", # Gemini API provider + model="gemini-2.0-flash", + ), + ) +) +print(f"✅ Gemini API: {gemini_response.completion_id}") + +print("\n" + "=" * 50) +print("Summary:") +print("- Both providers support Gemini models") +print("- Vertex AI: GCP integration, enterprise features") +print("- Gemini API: Simple API key, quick setup") +print("- Choose based on your infrastructure needs") +print("=" * 50) diff --git a/src/freeplay/resources/prompts.py b/src/freeplay/resources/prompts.py index e79aba8..82094bc 100644 --- a/src/freeplay/resources/prompts.py +++ b/src/freeplay/resources/prompts.py @@ -599,7 +599,7 @@ def __flavor_to_provider(flavor: str) -> str: "azure_openai_chat": "azure", "anthropic_chat": "anthropic", "openai_chat": "openai", - "gemini_chat": "vertex", + "gemini_chat": "vertex", # Default to vertex for backward compatibility } provider = flavor_provider.get(flavor) if not provider: diff --git a/src/freeplay/resources/recordings.py b/src/freeplay/resources/recordings.py index adc79c9..00fe88b 100644 --- a/src/freeplay/resources/recordings.py +++ b/src/freeplay/resources/recordings.py @@ -39,6 +39,21 @@ class UsageTokens: @dataclass class CallInfo: + """ + Information about an LLM call. + + Attributes: + provider: LLM provider name. Supported: "openai", "anthropic", "azure", + "bedrock", "vertex", "gemini". Use "vertex" for Vertex AI (GCP), + "gemini" for Gemini API (simple API key). + model: Model identifier (e.g., "gpt-4", "gemini-2.0-flash") + start_time: Unix timestamp when the call started + end_time: Unix timestamp when the call ended + model_parameters: Model configuration (temperature, max_tokens, etc.) + provider_info: Additional provider-specific metadata + usage: Token usage information + api_style: API style (chat, completion, etc.) + """ provider: Optional[str] = None model: Optional[str] = None start_time: Optional[float] = None diff --git a/tests/test_freeplay.py b/tests/test_freeplay.py index d90e61f..b149056 100644 --- a/tests/test_freeplay.py +++ b/tests/test_freeplay.py @@ -1422,7 +1422,7 @@ def test_prompt_format_with_tool_schema_gemini(self) -> None: environment="environment", model_parameters=LLMParameters({}), provider_info=None, - provider="vertex", + provider="gemini", model="gemini-pro", flavor_name="gemini_chat", ) @@ -1458,6 +1458,28 @@ def test_prompt_format_with_tool_schema_gemini(self) -> None: except ImportError: self.skipTest("Vertex AI SDK not installed") + def test_flavor_to_provider_mapping(self) -> None: + """Test that flavor names correctly map to provider names""" + from freeplay.resources.adapters import MissingFlavorError + + # Test the private method through its name-mangled form + # Type checker doesn't like accessing private methods, so we use type: ignore + flavor_to_provider = ( # type: ignore[attr-defined] + FilesystemTemplateResolver._FilesystemTemplateResolver__flavor_to_provider # type: ignore[attr-defined] + ) + + # Test all supported flavor mappings + self.assertEqual(flavor_to_provider("openai_chat"), "openai") + self.assertEqual(flavor_to_provider("azure_openai_chat"), "azure") + self.assertEqual(flavor_to_provider("anthropic_chat"), "anthropic") + self.assertEqual( + flavor_to_provider("gemini_chat"), "gemini" + ) # Key test: gemini_chat -> gemini + + # Test that unknown flavors raise an error + with self.assertRaises(MissingFlavorError): + flavor_to_provider("unknown_flavor") + def test_prompt_format_with_output_schema_openai(self) -> None: messages: List[TemplateMessage] = [ TemplateChatMessage(role="system", content="System message"),