Skip to content

Commit 62e05e3

Browse files
Azuki-barmdrxy
andauthored
feat(genai): add labels support (#1503)
<!-- # Thank you for contributing to LangChain-google! --> <!-- ## Checklist for PR Creation - [x] PR Title: "<type>[optional scope]: <description>" - Where type is one of: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert, release - Scope is used to specifiy the package targeted. Options are: genai, vertex, community, infra (repo-level) - [x] PR Description and Relevant issues: - Description of the change - Relevant issues (if applicable) - Any dependencies required for this change - [x] Add Tests and Docs: - If adding a new integration: 1. Include a test for the integration (preferably unit tests that do not rely on network access) 2. Add an example notebook showing its use (place in the `docs/docs/integrations` directory) - [x] Lint and Test: - Run `make format`, `make lint`, and `make test` from the root of the package(s) you've modified - See contribution guidelines for more: https://github.com/langchain-ai/langchain-google/blob/main/README.md#contribute-code --> <!-- ## Additional guidelines - [x] PR title and description are appropriate - [x] Necessary tests and documentation have been added - [x] Lint and tests pass successfully - [x] The following additional guidelines are adhered to: - Optional dependencies are imported within functions - No unnecessary dependencies added to pyproject.toml files (except those required for unit tests) - PR doesn't touch more than one package - Changes are backwards compatible --> ## Description <!-- e.g. "Implement user authentication feature" --> Added the `labels` field to `ChatGoogleGenerativeAI`. This allows users to attach custom metadata to API requests and track billing breakdowns. This feature was already implemented in `ChatVertexAI`, and this change makes it available in `ChatGoogleGenerativeAI` as well. ## Relevant issues <!-- e.g. "Fixes #000" --> fixes #1496 ## Type <!-- Select the type of Pull Request --> <!-- Keep only the necessary ones --> 🆕 New Feature ## Changes(optional) <!-- List of changes --> - Added `labels: dict[str, str] | None` field to `_BaseGoogleGenerativeAI` - Implemented logic to pass `labels` to `GenerateContentConfig` in `ChatGoogleGenerativeAI` - Added unit tests to verify that labels is passed correctly ## Testing(optional) <!-- Test procedure --> ```python llm = ChatGoogleGenerativeAI( model="gemini-2.0-flash", labels={"env": "production", "team": "ml"}, ) ``` <!-- Test result --> Unit tests added: - `labels` is correctly passed to `GenerateContentConfig` - `labels` defaults to None when not specified ## Note(optional) <!-- Information about the errors fixed by PR --> <!-- Remaining issue or something --> <!-- Other information about PR --> `ChatVertexAI` includes labels validation [^1], but this was omitted in `ChatGoogleGenerativeAI` to align with other fields that do not have validation. [^1]: https://github.com/langchain-ai/langchain-google/blob/306f6ee3d8d03672d95f8907edefa61e41bf02d3/libs/vertexai/langchain_google_vertexai/chat_models.py#L1858-L1868 Google GenAI SDK already supports the `labels` field at here. https://googleapis.github.io/python-genai/genai.html#genai.types.GenerateContentConfig.labels --------- Co-authored-by: Mason Daugherty <mason@langchain.dev> Co-authored-by: Mason Daugherty <github@mdrxy.com>
1 parent 474b8fb commit 62e05e3

3 files changed

Lines changed: 65 additions & 0 deletions

File tree

libs/genai/langchain_google_genai/_common.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,16 @@ class _BaseGoogleGenerativeAI(BaseModel):
506506
infrastructure.
507507
"""
508508

509+
labels: dict[str, str] | None = Field(default=None)
510+
"""User-defined key-value metadata for organizing and filtering billing reports.
511+
512+
Attach labels to categorize API usage by team, environment, or feature.
513+
514+
Can be overridden per-request via invoke kwargs.
515+
516+
See: https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/add-labels-to-api-calls
517+
"""
518+
509519
@model_validator(mode="after")
510520
def _resolve_project_from_credentials(self) -> Self:
511521
"""Extract project from credentials if not explicitly set.

libs/genai/langchain_google_genai/chat_models.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2797,6 +2797,10 @@ def _prepare_request(
27972797

27982798
image_config = kwargs.pop("image_config", None)
27992799

2800+
labels = kwargs.pop("labels", None)
2801+
if labels is None:
2802+
labels = self.labels
2803+
28002804
_consumed_kwargs = {
28012805
"thinking_budget",
28022806
"thinking_level",
@@ -2824,6 +2828,7 @@ def _prepare_request(
28242828
timeout=timeout,
28252829
max_retries=max_retries,
28262830
image_config=image_config,
2831+
labels=labels,
28272832
**remaining_kwargs,
28282833
)
28292834

@@ -2971,6 +2976,7 @@ def _build_request_config(
29712976
timeout: int | None = None,
29722977
max_retries: int | None = None,
29732978
image_config: dict[str, Any] | None = None,
2979+
labels: dict[str, str] | None = None,
29742980
**kwargs: Any,
29752981
) -> GenerateContentConfig:
29762982
"""Build the final request configuration."""
@@ -3001,6 +3007,7 @@ def _build_request_config(
30013007
system_instruction=system_instruction,
30023008
http_options=http_options,
30033009
image_config=image_config_obj,
3010+
labels=labels,
30043011
**params.model_dump(exclude_unset=True),
30053012
**kwargs,
30063013
)

libs/genai/tests/unit_tests/test_chat_models.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4921,3 +4921,51 @@ def test_openai_style_bind_with_response_format() -> None:
49214921
strict=True,
49224922
)
49234923
assert bound4 is not None
4924+
4925+
4926+
def test_labels_passed_to_generate_content_config() -> None:
4927+
"""Test that `labels` is properly passed to `GenerateContentConfig`."""
4928+
llm = ChatGoogleGenerativeAI(
4929+
model=MODEL_NAME,
4930+
google_api_key=SecretStr(FAKE_API_KEY),
4931+
labels={"env": "production", "team": "ml"},
4932+
)
4933+
messages: list[BaseMessage] = [HumanMessage(content="Hello")]
4934+
request = llm._prepare_request(messages)
4935+
config = request["config"]
4936+
4937+
assert config.labels is not None
4938+
assert config.labels == {"env": "production", "team": "ml"}
4939+
4940+
4941+
def test_labels_none_by_default() -> None:
4942+
"""Test that `labels` is `None` by default."""
4943+
llm = ChatGoogleGenerativeAI(
4944+
model=MODEL_NAME,
4945+
google_api_key=SecretStr(FAKE_API_KEY),
4946+
)
4947+
assert llm.labels is None
4948+
4949+
messages: list[BaseMessage] = [HumanMessage(content="Hello")]
4950+
request = llm._prepare_request(messages)
4951+
config = request["config"]
4952+
4953+
assert config.labels is None
4954+
4955+
4956+
def test_labels_override_in_invoke() -> None:
4957+
"""Test that labels can be overridden at invocation time via kwargs."""
4958+
llm = ChatGoogleGenerativeAI(
4959+
model=MODEL_NAME,
4960+
google_api_key=SecretStr(FAKE_API_KEY),
4961+
labels={"env": "production", "team": "ml"},
4962+
)
4963+
messages: list[BaseMessage] = [HumanMessage(content="Hello")]
4964+
4965+
# Override labels via kwargs
4966+
request = llm._prepare_request(
4967+
messages, labels={"env": "staging", "request_id": "123"}
4968+
)
4969+
config = request["config"]
4970+
4971+
assert config.labels == {"env": "staging", "request_id": "123"}

0 commit comments

Comments
 (0)