Skip to content

Commit 89e360b

Browse files
authored
Merge pull request #354 from CyberAgentAILab/feature/genai-edit-without-input
feat: add pinjected-genai package and type stubs
2 parents ad6c963 + 77df3ab commit 89e360b

27 files changed

Lines changed: 1720 additions & 40 deletions

.serena/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/cache

packages/gcp/src/pinjected_gcp/__init__.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,9 @@
88

99
# Re-export commonly used items from auth
1010
from .auth import (
11-
gcp_credentials,
1211
gcp_credentials_from_env,
1312
gcp_credentials_from_file,
1413
gcp_credentials_from_dict,
15-
gcp_project_id,
16-
gcp_project_id_from_env,
17-
gcp_project_id_from_dict,
1814
)
1915

2016
# Re-export from secrets
@@ -67,13 +63,9 @@
6763
"a_list_gcp_secrets",
6864
"a_upload_gcs",
6965
"auth",
70-
"gcp_credentials",
7166
"gcp_credentials_from_dict",
7267
"gcp_credentials_from_env",
7368
"gcp_credentials_from_file",
74-
"gcp_project_id",
75-
"gcp_project_id_from_dict",
76-
"gcp_project_id_from_env",
7769
"gcp_secret_manager_client",
7870
"gcp_secret_value",
7971
"gcp_secret_value_cached",
@@ -83,9 +75,4 @@
8375
]
8476

8577
# Combined design for all GCP functionality
86-
__design__ = (
87-
design(logger=loguru.logger)
88-
+ auth.__design__
89-
+ secrets.__design__
90-
+ storage.__design__
91-
)
78+
__design__ = design(logger=loguru.logger)

packages/gcp/src/pinjected_gcp/auth/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@
44
gcp_credentials_from_file,
55
gcp_credentials_from_dict,
66
gcp_credentials_from_env,
7-
gcp_credentials,
87
__design__ as auth_design,
98
)
109

1110
__all__ = [
1211
"auth_design",
13-
"gcp_credentials",
1412
"gcp_credentials_from_dict",
1513
"gcp_credentials_from_env",
1614
"gcp_credentials_from_file",

packages/gcp/src/pinjected_gcp/auth/credentials.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from google.auth.credentials import Credentials
1010
from loguru import logger
1111
from pinjected import design, instance
12-
from pinjected.di.iproxy import IProxy
1312

1413

1514
@instance
@@ -37,7 +36,7 @@ def gcp_service_account_dict_from_file(
3736
@instance
3837
def gcp_credentials_from_file(
3938
gcp_service_account_path: Path,
40-
gcp_scopes: list[str],
39+
# gcp_scopes: list[str],
4140
) -> Credentials:
4241
"""
4342
Create GCP credentials from a service account JSON file.
@@ -57,7 +56,7 @@ def gcp_credentials_from_file(
5756
logger.info(f"Loading GCP credentials from {gcp_service_account_path}")
5857
return service_account.Credentials.from_service_account_file(
5958
str(gcp_service_account_path),
60-
scopes=gcp_scopes,
59+
# scopes=gcp_scopes,
6160
)
6261

6362

@@ -150,7 +149,7 @@ def gcp_project_id_from_env() -> str:
150149

151150

152151
# Default scopes
153-
default_gcp_scopes = IProxy.bind(["https://www.googleapis.com/auth/cloud-platform"])
152+
default_gcp_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
154153

155154
# Design for auth module
156155
__design__ = design(
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import Any, Dict
2+
from google.oauth2.credentials import Credentials
3+
from pinjected import IProxy
4+
5+
default_gcp_scopes: Any
6+
__design__: Any
7+
8+
gcp_service_account_dict_from_file: IProxy[Dict[str, Any]]
9+
gcp_credentials_from_file: IProxy[Credentials]
10+
gcp_credentials_from_dict: IProxy[Credentials]
11+
gcp_credentials_from_env: IProxy[Credentials]
12+
gcp_project_id_from_dict: IProxy[str]
13+
gcp_project_id_from_env: IProxy[str]

packages/pinjected-genai/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# pinjected-genai
2+
3+
Google GenAI (Gemini) bindings for the pinjected library.
4+
5+
## Features
6+
7+
- Seamless integration with Google GenAI SDK
8+
- Support for Gemini 2.0 Flash image generation
9+
- Async support with @injected decorators
10+
- Rate limiting and retry logic
11+
12+
## Installation
13+
14+
```bash
15+
pip install pinjected-genai
16+
```
17+
18+
## Usage
19+
20+
```python
21+
from pinjected_genai import genai_client, generate_image
22+
23+
# Use with dependency injection
24+
@injected
25+
async def my_function(generate_image, /):
26+
result = await generate_image("A cute baby turtle in 3D art style")
27+
return result
28+
```
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
[build-system]
2+
requires = ["hatchling==1.26.3"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "pinjected-genai"
7+
version = "0.1.0"
8+
description = "Google Vertex AI (Gemini) bindings for pinjected library"
9+
readme = "README.md"
10+
requires-python = ">=3.10"
11+
license = "MIT"
12+
authors = [
13+
{ name = "Kento Masui", email = "nameissoap@gmail.com" }
14+
]
15+
classifiers = [
16+
"Programming Language :: Python :: 3",
17+
"License :: OSI Approved :: MIT License",
18+
"Operating System :: OS Independent",
19+
]
20+
21+
dependencies = [
22+
"pinjected",
23+
"pinjected-gcp",
24+
"google-genai>=0.1.0",
25+
"google-auth>=2.0.0",
26+
"injected-utils>=0.1.32",
27+
"pillow",
28+
"loguru",
29+
"filelock",
30+
"reactivex>=4.0.4",
31+
"pinjected-rate-limit>=0.1.0",
32+
"tenacity>=9.0.0",
33+
]
34+
35+
[tool.hatch.metadata]
36+
allow-direct-references = true
37+
38+
[tool.hatch.build.targets.wheel]
39+
packages = ["src/pinjected_genai"]
40+
41+
[tool.pytest.ini_options]
42+
pythonpath = ["src/pinjected_genai"]
43+
asyncio_mode = "strict"
44+
asyncio_default_fixture_loop_scope = "function"
45+
markers = [
46+
"integration: marks tests as integration tests (deselect with '-m \"not integration\"')",
47+
"visual: marks tests for visual verification of generated images (run with --no-skip)",
48+
]
49+
50+
[tool.uv.sources]
51+
pinjected = { workspace = true }
52+
pinjected-gcp = { workspace = true }
53+
injected-utils = { workspace = true }
54+
pinjected-rate-limit = { workspace = true }
55+
56+
[dependency-groups]
57+
dev = [
58+
"pytest>=8.1.1,<9",
59+
"pytest-asyncio>=0.24.0",
60+
"matplotlib>=3.5.0",
61+
]

packages/pinjected-genai/src/__init__.py

Whitespace-only changes.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from pathlib import Path
2+
3+
from pinjected_genai.clients import (
4+
GenAIAuthClient,
5+
genai_auth_client,
6+
genai_auth_client_adc,
7+
genai_client,
8+
genai_location,
9+
genai_model_name,
10+
)
11+
from pinjected_genai.image_generation import (
12+
GeneratedImage,
13+
GenerationResult,
14+
a_describe_image__genai,
15+
a_edit_image__genai,
16+
a_generate_image__genai,
17+
)
18+
19+
from pinjected import *
20+
21+
__version__ = "0.1.0"
22+
23+
__all__ = [
24+
"GenAIAuthClient",
25+
"GeneratedImage",
26+
"GenerationResult",
27+
"a_describe_image__genai",
28+
"a_edit_image__genai",
29+
"a_generate_image__genai",
30+
"genai_auth_client",
31+
"genai_auth_client_adc",
32+
"genai_client",
33+
"genai_location",
34+
"genai_model_name",
35+
]
36+
37+
default_design = design(
38+
cache_root_path=Path("~/.cache/pinjected_genai").expanduser(),
39+
)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from dataclasses import dataclass
2+
3+
from google import genai
4+
from google.auth import default
5+
from google.auth.credentials import Credentials
6+
from loguru import logger
7+
8+
from pinjected import instance
9+
10+
11+
@dataclass
12+
class GenAIAuthClient:
13+
"""Authentication client for Google Gen AI with Vertex AI support."""
14+
15+
credentials: Credentials
16+
project_id: str
17+
location: str = "global"
18+
19+
def create_client(self) -> genai.Client:
20+
"""Create a Gen AI client with Vertex AI and ADC support."""
21+
# Use vertexai=True to enable Vertex AI mode with ADC
22+
client = genai.Client(
23+
vertexai=True,
24+
project=self.project_id,
25+
location=self.location,
26+
credentials=self.credentials,
27+
)
28+
logger.info(
29+
f"Created Gen AI client with Vertex AI mode for project {self.project_id} "
30+
f"in {self.location}"
31+
)
32+
return client
33+
34+
35+
@instance
36+
def genai_auth_client_adc() -> GenAIAuthClient:
37+
"""Default Gen AI auth client using Application Default Credentials."""
38+
logger.info("Creating Gen AI auth client using Application Default Credentials")
39+
credentials, project = default()
40+
41+
if not project:
42+
raise ValueError(
43+
"Could not determine GCP project ID from Application Default Credentials. "
44+
"Please set GOOGLE_CLOUD_PROJECT environment variable or use gcloud config."
45+
)
46+
47+
logger.info(f"Using ADC with project: {project}")
48+
return GenAIAuthClient(credentials=credentials, project_id=project)
49+
50+
51+
@instance
52+
def genai_auth_client(
53+
genai_auth_client_adc: GenAIAuthClient,
54+
genai_location: str,
55+
) -> GenAIAuthClient:
56+
"""Gen AI authentication client - default uses ADC, can be overridden via injection."""
57+
# Update location from injection
58+
genai_auth_client_adc.location = genai_location
59+
return genai_auth_client_adc
60+
61+
62+
@instance
63+
def genai_location() -> str:
64+
"""Default GCP location for Gen AI API.
65+
66+
Note: Gemini 2.5 Flash Image Preview (nano-banana) is only available
67+
on the global endpoint, not regional endpoints.
68+
"""
69+
return "global"
70+
71+
72+
@instance
73+
def genai_client(
74+
genai_auth_client: GenAIAuthClient,
75+
) -> genai.Client:
76+
"""Singleton Gen AI client with Vertex AI and ADC support."""
77+
client = genai_auth_client.create_client()
78+
logger.info(
79+
f"Created Gen AI client for project {genai_auth_client.project_id} "
80+
f"in {genai_auth_client.location}"
81+
)
82+
return client
83+
84+
85+
@instance
86+
def genai_model_name() -> str:
87+
"""Default model name for image generation (nano-banana)."""
88+
return "gemini-2.5-flash-image-preview"

0 commit comments

Comments
 (0)