Skip to content
This repository was archived by the owner on Dec 20, 2025. It is now read-only.

Commit f4d4f35

Browse files
azmavethclaude
andcommitted
feat: add OAuth2 support for Gemini APIs and fix LM Studio streaming
## OAuth2 Support for Gemini APIs - Add comprehensive OAuth2 setup scripts and documentation - Implement OAuth2 tests for Corpus, Document, Chunk, QA, and Permissions APIs - Create helper scripts for OAuth2 token management and test data cleanup - Update test_helper.exs to conditionally run OAuth2 tests based on token availability - Add :oauth2 test tag to separate OAuth2 tests from regular integration tests ## Fix LM Studio Streaming - Fix streaming implementation to use HTTPClient.stream_request instead of post_json - Add parse_stream_chunk function to handle SSE format - Support both 'content' and 'reasoning_content' fields from newer models - Update integration test to be more flexible about finish_reason presence ## API Consistency Updates - Update Document and Chunk modules to use consistent authentication patterns - Change from map-based auth parameters to keyword lists for consistency - Fix batch operations to properly handle parent document references ## Test Improvements - Fix compilation warnings in Chunk tests (wrong function arity) - Update Permissions tests to use :oauth2 tag instead of :integration - Make QA tests use correct API signature with model parameter - Add flexibility to error assertions to handle different error formats 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 5150f88 commit f4d4f35

26 files changed

+2383
-336
lines changed

docs/gemini/OAUTH2_SETUP_GUIDE.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Setting Up Real Google OAuth2 Credentials for Gemini API
2+
3+
This guide will walk you through obtaining real OAuth2 credentials for testing the Gemini Permissions API and other OAuth2-only features.
4+
5+
## Prerequisites
6+
7+
1. A Google Account
8+
2. A Google Cloud Project (or create a new one)
9+
3. Gemini API enabled in your project
10+
11+
## Step 1: Create a Google Cloud Project
12+
13+
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
14+
2. Click on the project dropdown at the top
15+
3. Click "New Project"
16+
4. Enter a project name (e.g., "ExLLM Testing")
17+
5. Click "Create"
18+
19+
## Step 2: Enable the Gemini API
20+
21+
1. In your project, go to "APIs & Services" > "Library"
22+
2. Search for "Generative Language API" or "Gemini API"
23+
3. Click on it and press "Enable"
24+
25+
## Step 3: Create OAuth2 Credentials
26+
27+
1. Go to "APIs & Services" > "Credentials"
28+
2. Click "+ CREATE CREDENTIALS" > "OAuth client ID"
29+
3. If prompted, configure the OAuth consent screen first:
30+
- Choose "External" user type (for testing)
31+
- Fill in required fields:
32+
- App name: "ExLLM OAuth Test"
33+
- User support email: your email
34+
- Developer contact: your email
35+
- Add scopes:
36+
- Click "Add or Remove Scopes"
37+
- Add these scopes:
38+
- `https://www.googleapis.com/auth/cloud-platform`
39+
- `openid`
40+
- `https://www.googleapis.com/auth/userinfo.email`
41+
- Add your email as a test user
42+
- Save and continue
43+
44+
4. Now create the OAuth client ID:
45+
- Application type: "Desktop app" (easiest for CLI testing)
46+
- Name: "ExLLM CLI"
47+
- Click "Create"
48+
49+
5. Download the credentials JSON file
50+
- Click the download button next to your new OAuth client
51+
- Save it as `client_secret.json` in a secure location
52+
53+
## Step 4: Set Up Environment Variables
54+
55+
```bash
56+
# Extract from the downloaded JSON file:
57+
export GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com"
58+
export GOOGLE_CLIENT_SECRET="your-client-secret"
59+
60+
# Or for permanent setup, add to your shell profile:
61+
echo 'export GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com"' >> ~/.bashrc
62+
echo 'export GOOGLE_CLIENT_SECRET="your-client-secret"' >> ~/.bashrc
63+
```
64+
65+
## Step 5: Run the OAuth2 Setup Script
66+
67+
```bash
68+
# From the ex_llm directory
69+
elixir scripts/setup_oauth2.exs
70+
```
71+
72+
The script will:
73+
1. Start a local web server on port 8080
74+
2. Open your browser to Google's authorization page
75+
3. After you authorize, capture the authorization code
76+
4. Exchange it for access and refresh tokens
77+
5. Save the tokens to `.gemini_tokens`
78+
79+
## Step 6: Test the OAuth2 Integration
80+
81+
```bash
82+
# Run the OAuth2 permission tests
83+
mix test test/ex_llm/adapters/gemini/permissions_oauth2_test.exs
84+
85+
# Or test manually in IEx:
86+
iex -S mix
87+
88+
# In IEx:
89+
{:ok, tokens} = File.read!(".gemini_tokens") |> Jason.decode!()
90+
{:ok, result} = ExLLM.Gemini.Permissions.list_permissions(
91+
"tunedModels/test-model",
92+
oauth_token: tokens["access_token"]
93+
)
94+
```
95+
96+
## Troubleshooting
97+
98+
### "redirect_uri_mismatch" Error
99+
- Make sure you're using "Desktop app" type for the OAuth client
100+
- The redirect URI should be `http://localhost:8080/callback`
101+
102+
### "invalid_client" Error
103+
- Double-check your CLIENT_ID and CLIENT_SECRET
104+
- Make sure you're using the correct credentials from the downloaded JSON
105+
106+
### "access_denied" Error
107+
- Make sure your Google account is added as a test user in the OAuth consent screen
108+
- Try clearing browser cookies for accounts.google.com
109+
110+
### Token Expired
111+
- Run `elixir scripts/refresh_oauth2_token.exs` to refresh
112+
- Tokens expire after 1 hour
113+
114+
## Security Notes
115+
116+
1. **Never commit credentials**: The `.gemini_tokens` file is already in `.gitignore`
117+
2. **Keep client_secret.json secure**: Don't commit this file
118+
3. **Use environment variables**: Don't hardcode credentials
119+
4. **Rotate credentials regularly**: Revoke and recreate if compromised
120+
121+
## Next Steps
122+
123+
Once you have OAuth2 working:
124+
1. Test the Permissions API for managing tuned model access
125+
2. Test the Corpus API for document management
126+
3. Implement automatic token refresh in your application

docs/gemini/OAUTH2_TEST_SETUP.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# OAuth2 API Test Setup Guide
2+
3+
This guide explains how to set up test data and accounts for comprehensive OAuth2 API testing.
4+
5+
## Overview
6+
7+
The OAuth2-protected APIs in Gemini include:
8+
1. **Corpus Management API** - Document collections for semantic retrieval
9+
2. **Document Management API** - Documents within corpora
10+
3. **Chunk Management API** - Text chunks within documents
11+
4. **Question Answering API** - Semantic Q&A using corpora
12+
5. **Permissions API** - Access control for tuned models
13+
14+
## Test Data Requirements
15+
16+
### 1. Basic OAuth2 Setup (Required)
17+
18+
```bash
19+
# Set up OAuth2 credentials
20+
elixir scripts/setup_oauth2.exs
21+
22+
# This creates .gemini_tokens with access to:
23+
# - https://www.googleapis.com/auth/cloud-platform
24+
# - https://www.googleapis.com/auth/generative-language.tuning
25+
# - https://www.googleapis.com/auth/generative-language.retriever
26+
```
27+
28+
### 2. Corpus/Document/Chunk APIs (Automatic)
29+
30+
The test suite automatically creates and cleans up:
31+
- Test corpora with unique names
32+
- Documents with metadata
33+
- Chunks with searchable content
34+
35+
No manual setup required - tests are self-contained.
36+
37+
### 3. Question Answering API (Automatic)
38+
39+
Tests automatically create:
40+
- A corpus with Elixir documentation
41+
- Multiple chunks about Elixir features
42+
- Queries are run against this test data
43+
44+
### 4. Permissions API (Manual Setup Required)
45+
46+
To fully test the Permissions API, you need a tuned model:
47+
48+
#### Option A: Use Gemini AI Studio (Easiest)
49+
1. Go to [Google AI Studio](https://aistudio.google.com/)
50+
2. Click "Tune a model" in the left sidebar
51+
3. Select a base model (e.g., Gemini 1.5 Flash)
52+
4. Upload training data (even a small dataset works)
53+
5. Start tuning (takes 1-2 hours)
54+
6. Note the model name (e.g., `tunedModels/my-test-model-123`)
55+
56+
#### Option B: Use the API (Advanced)
57+
```elixir
58+
# Create a tuning job via API
59+
{:ok, operation} = ExLLM.Gemini.Tuning.create_tuning_job(
60+
"models/gemini-1.5-flash-001-tuning",
61+
%{
62+
display_name: "Test Model for Permissions",
63+
training_examples: [
64+
%{
65+
text_input: "What is ExLLM?",
66+
output: "ExLLM is a unified Elixir client for Large Language Models."
67+
},
68+
# Add more examples...
69+
]
70+
},
71+
oauth_token: token
72+
)
73+
```
74+
75+
#### Setting the Test Model
76+
```bash
77+
# Set environment variable with your tuned model
78+
export TEST_TUNED_MODEL="tunedModels/your-actual-model-name"
79+
80+
# Run permission tests
81+
mix test test/ex_llm/adapters/gemini/oauth2_apis_test.exs --only requires_tuned_model
82+
```
83+
84+
## Running OAuth2 Tests
85+
86+
### Run All OAuth2 Tests
87+
```bash
88+
# Ensure you have valid tokens
89+
mix test --only oauth2
90+
```
91+
92+
### Run Specific OAuth2 API Tests
93+
```bash
94+
# Corpus Management
95+
mix test test/ex_llm/adapters/gemini/oauth2_apis_test.exs:"Corpus Management API"
96+
97+
# Document Management
98+
mix test test/ex_llm/adapters/gemini/oauth2_apis_test.exs:"Document Management API"
99+
100+
# Chunk Management
101+
mix test test/ex_llm/adapters/gemini/oauth2_apis_test.exs:"Chunk Management API"
102+
103+
# Question Answering
104+
mix test test/ex_llm/adapters/gemini/oauth2_apis_test.exs:"Question Answering API"
105+
106+
# Permissions (requires tuned model)
107+
mix test test/ex_llm/adapters/gemini/oauth2_apis_test.exs:"Permissions API"
108+
```
109+
110+
### Token Management
111+
112+
```bash
113+
# Refresh expired token
114+
elixir scripts/refresh_oauth2_token.exs
115+
116+
# Check token validity
117+
iex -S mix
118+
iex> {:ok, tokens} = File.read!(".gemini_tokens") |> Jason.decode!()
119+
iex> tokens["expires_at"] # Check expiration time
120+
```
121+
122+
## Test Data Cleanup
123+
124+
The test suite automatically cleans up all created resources:
125+
- Corpora are deleted after each test
126+
- Documents and chunks are deleted with their parent corpus
127+
- Uses `on_exit` callbacks to ensure cleanup even on test failure
128+
129+
## Quotas and Limits
130+
131+
Be aware of API quotas:
132+
- **Corpora**: Up to 5 per project
133+
- **Documents**: Up to 10,000 per corpus
134+
- **Chunks**: Up to 1,000,000 per corpus
135+
- **Request rate**: Check your project's quota in Google Cloud Console
136+
137+
## Troubleshooting
138+
139+
### "Request had insufficient authentication scopes"
140+
- Re-run `elixir scripts/setup_oauth2.exs` with updated scopes
141+
- Ensure all required scopes are included
142+
143+
### "Quota exceeded"
144+
- Delete old test corpora manually:
145+
```elixir
146+
{:ok, list} = Corpus.list_corpora([], oauth_token: token)
147+
Enum.each(list.corpora, fn corpus ->
148+
if String.contains?(corpus.display_name, "test") do
149+
Corpus.delete_corpus(corpus.name, oauth_token: token, force: true)
150+
end
151+
end)
152+
```
153+
154+
### "Invalid corpus/document name"
155+
- Names must follow the pattern: `corpora/[a-z0-9-]+`
156+
- No uppercase letters or special characters except hyphens
157+
158+
## Best Practices
159+
160+
1. **Use unique names**: Include timestamps or random IDs in test data names
161+
2. **Clean up resources**: Always use `on_exit` callbacks
162+
3. **Test incrementally**: Build corpus → add documents → add chunks → query
163+
4. **Mock when possible**: For unit tests, mock OAuth2 responses
164+
5. **Monitor quotas**: Check Google Cloud Console for usage
165+
166+
## Example Test Data Structure
167+
168+
```
169+
corpus: "test-corpus-12345"
170+
├── document: "elixir-guide"
171+
│ ├── chunk: "Elixir is a dynamic, functional language..."
172+
│ ├── chunk: "Pattern matching is powerful..."
173+
│ └── chunk: "GenServer implements client-server..."
174+
└── document: "phoenix-guide"
175+
├── chunk: "Phoenix is a web framework..."
176+
└── chunk: "LiveView enables real-time features..."
177+
```
178+
179+
This structure allows testing:
180+
- Corpus queries across all documents
181+
- Document-specific queries
182+
- Metadata filtering
183+
- Semantic retrieval
184+
- Question answering

0 commit comments

Comments
 (0)