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
33 changes: 33 additions & 0 deletions tests/unit/vertexai/genai/replays/test_skills_get.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Tests the skills.get() method against the autopush endpoint."""

from google.api_core import exceptions
from tests.unit.vertexai.genai.replays import pytest_helper
import pytest

PROJECT_ID = "demo-project"
REGION = "us-central1"
SKILL_ID = "7184367305562783744"
# target the autopush sandbox endpoint for the Skill Registry API
ENDPOINT = f"{REGION}-autopush-aiplatform.sandbox.googleapis.com"


pytestmark = pytest_helper.setup(
file=__file__,
globals_for_file=globals(),
)


def test_get_skill(client): # client fixture is injected by pytest_helper.setup
"""Tests the skills.get() method against the autopush endpoint."""

client._api_client._http_options.base_url = (
"https://us-central1-autopush-aiplatform.sandbox.googleapis.com"
)
skill_name = f"projects/{PROJECT_ID}/locations/{REGION}/skills/{SKILL_ID}"

try:
skill = client.skills.get(name=skill_name)
assert skill.name == skill_name

except exceptions.GoogleAPIError as e:
pytest.fail(f"Error calling client.skills.get(): {e}")
46 changes: 46 additions & 0 deletions tests/unit/vertexai/genai/test_genai_skills.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# //third_party/py/google/cloud/aiplatform/tests/unit/vertexai/genai/test_genai_skills.py
import json
from unittest import mock
from vertexai import _genai as genai
from vertexai._genai import client as vertexai_client
from google.genai import types as genai_types
import pytest


@pytest.fixture
def skills_client():
creds = mock.MagicMock()
creds.token = "test_token"
client = vertexai_client.Client(
project="test-project", location="test-location", credentials=creds
)
return client.skills


class TestGenaiSkills:
mock_get_skill_response = {
"name": "projects/test-project/locations/test-location/skills/test-skill",
"displayName": "My Test Skill",
}

def test_get_skill(self, skills_client):
"""Tests the get_skill method."""
with mock.patch.object(
skills_client._api_client, "request"
) as request_mock:
request_mock.return_value = genai_types.HttpResponse(
body=json.dumps(self.mock_get_skill_response)
)
skill_name = (
"projects/test-project/locations/test-location/skills/test-skill"
)
skill = skills_client.get(name=skill_name)
request_mock.assert_called_with(
"get",
skill_name,
{"_url": {"name": skill_name}},
None,
)
assert isinstance(skill, genai.types.Skill)
assert skill.name == skill_name
assert skill.display_name == "My Test Skill"
21 changes: 21 additions & 0 deletions vertexai/_genai/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
prompt_optimizer as prompt_optimizer_module,
)
from vertexai._genai import prompts as prompts_module
from vertexai._genai import skills as skills_module
from vertexai._genai import live as live_module


Expand All @@ -52,6 +53,7 @@ def __init__(self, api_client: genai_client.BaseApiClient): # type: ignore[name
self._prompt_optimizer: Optional[ModuleType] = None
self._prompts: Optional[ModuleType] = None
self._datasets: Optional[ModuleType] = None
self._skills: Optional[ModuleType] = None

@property
@_common.experimental_warning(
Expand Down Expand Up @@ -124,6 +126,15 @@ def datasets(self) -> "datasets_module.AsyncDatasets":
)
return self._datasets.AsyncDatasets(self._api_client) # type: ignore[no-any-return]

@property
def skills(self) -> "skills_module.AsyncSkills":
if self._skills is None:
self._skills = importlib.import_module(
".skills",
__package__,
)
return self._skills.AsyncSkills(self._api_client) # type: ignore[no-any-return]

async def aclose(self) -> None:
"""Closes the async client explicitly.

Expand Down Expand Up @@ -239,6 +250,7 @@ def __init__(
self._agent_engines: Optional[ModuleType] = None
self._prompts: Optional[ModuleType] = None
self._datasets: Optional[ModuleType] = None
self._skills: Optional[ModuleType] = None

@property
def evals(self) -> "evals_module.Evals":
Expand Down Expand Up @@ -335,3 +347,12 @@ def datasets(self) -> "datasets_module.Datasets":
__package__,
)
return self._datasets.Datasets(self._api_client) # type: ignore[no-any-return]

@property
def skills(self) -> "skills_module.Skills":
if self._skills is None:
self._skills = importlib.import_module(
".skills",
__package__,
)
return self._skills.Skills(self._api_client) # type: ignore[no-any-return]
190 changes: 190 additions & 0 deletions vertexai/_genai/skills.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# Code generated by the Google Gen AI SDK generator DO NOT EDIT.

import json
import logging
from typing import Any, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv

from . import types

logger = logging.getLogger("vertexai_genai.skills")


def _GetSkillRequestParameters_to_vertex(
from_object: Union[dict[str, Any], object],
parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
to_object: dict[str, Any] = {}
if getv(from_object, ["name"]) is not None:
setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

if getv(from_object, ["config"]) is not None:
setv(to_object, ["config"], getv(from_object, ["config"]))

return to_object


class Skills(_api_module.BaseModule):
"""Class for managing Skills in the Skill Registry."""

def get(
self, *, name: str, config: Optional[types.GetSkillConfigOrDict] = None
) -> types.Skill:
"""Gets a Skill."""

parameter_model = types._GetSkillRequestParameters(
name=name,
config=config,
)

request_url_dict: Optional[dict[str, str]]
if not self._api_client.vertexai:
raise ValueError(
"This method is only supported in the Gemini Enterprise Agent"
" Platform (previously known as Vertex AI) client."
)
else:
request_dict = _GetSkillRequestParameters_to_vertex(parameter_model)
request_url_dict = request_dict.get("_url")
if request_url_dict:
path = "{name}".format_map(request_url_dict)
else:
path = "{name}"

query_params = request_dict.get("_query")
if query_params:
path = f"{path}?{urlencode(query_params)}"
# TODO: remove the hack that pops config.
request_dict.pop("config", None)

http_options: Optional[types.HttpOptions] = None
if (
parameter_model.config is not None
and parameter_model.config.http_options is not None
):
http_options = parameter_model.config.http_options

request_dict = _common.convert_to_dict(request_dict)
request_dict = _common.encode_unserializable_types(request_dict)

response = self._api_client.request("get", path, request_dict, http_options)

response_dict = {} if not response.body else json.loads(response.body)

return_value = types.Skill._from_response(
response=response_dict,
kwargs=(
{
"config": {
"response_schema": getattr(
parameter_model.config, "response_schema", None
),
"response_json_schema": getattr(
parameter_model.config, "response_json_schema", None
),
"include_all_fields": getattr(
parameter_model.config, "include_all_fields", None
),
}
}
if getattr(parameter_model, "config", None)
else {}
),
)

self._api_client._verify_response(return_value)
return return_value


class AsyncSkills(_api_module.BaseModule):
"""Class for managing Skills in the Skill Registry."""

async def get(
self, *, name: str, config: Optional[types.GetSkillConfigOrDict] = None
) -> types.Skill:
"""Gets a Skill."""

parameter_model = types._GetSkillRequestParameters(
name=name,
config=config,
)

request_url_dict: Optional[dict[str, str]]
if not self._api_client.vertexai:
raise ValueError(
"This method is only supported in the Gemini Enterprise Agent"
" Platform (previously known as Vertex AI) client."
)
else:
request_dict = _GetSkillRequestParameters_to_vertex(parameter_model)
request_url_dict = request_dict.get("_url")
if request_url_dict:
path = "{name}".format_map(request_url_dict)
else:
path = "{name}"

query_params = request_dict.get("_query")
if query_params:
path = f"{path}?{urlencode(query_params)}"
# TODO: remove the hack that pops config.
request_dict.pop("config", None)

http_options: Optional[types.HttpOptions] = None
if (
parameter_model.config is not None
and parameter_model.config.http_options is not None
):
http_options = parameter_model.config.http_options

request_dict = _common.convert_to_dict(request_dict)
request_dict = _common.encode_unserializable_types(request_dict)

response = await self._api_client.async_request(
"get", path, request_dict, http_options
)

response_dict = {} if not response.body else json.loads(response.body)

return_value = types.Skill._from_response(
response=response_dict,
kwargs=(
{
"config": {
"response_schema": getattr(
parameter_model.config, "response_schema", None
),
"response_json_schema": getattr(
parameter_model.config, "response_json_schema", None
),
"include_all_fields": getattr(
parameter_model.config, "include_all_fields", None
),
}
}
if getattr(parameter_model, "config", None)
else {}
),
)

self._api_client._verify_response(return_value)
return return_value
16 changes: 16 additions & 0 deletions vertexai/_genai/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
from .common import _GetSandboxEnvironmentSnapshotRequestParameters
from .common import _GetSandboxEnvironmentTemplateOperationParameters
from .common import _GetSandboxEnvironmentTemplateRequestParameters
from .common import _GetSkillRequestParameters
from .common import _IngestEventsRequestParameters
from .common import _ListAgentEngineMemoryRequestParameters
from .common import _ListAgentEngineMemoryRevisionsRequestParameters
Expand Down Expand Up @@ -608,6 +609,9 @@
from .common import GetSandboxEnvironmentTemplateConfig
from .common import GetSandboxEnvironmentTemplateConfigDict
from .common import GetSandboxEnvironmentTemplateConfigOrDict
from .common import GetSkillConfig
from .common import GetSkillConfigDict
from .common import GetSkillConfigOrDict
from .common import IdentityType
from .common import Importance
from .common import IngestEventsConfig
Expand Down Expand Up @@ -1243,6 +1247,10 @@
from .common import SessionEventDict
from .common import SessionEventOrDict
from .common import SessionOrDict
from .common import Skill
from .common import SkillDict
from .common import SkillOrDict
from .common import SkillState
from .common import State
from .common import Strategy
from .common import StructuredMemoryConfig
Expand Down Expand Up @@ -2478,6 +2486,12 @@
"UpdatePromptConfig",
"UpdatePromptConfigDict",
"UpdatePromptConfigOrDict",
"GetSkillConfig",
"GetSkillConfigDict",
"GetSkillConfigOrDict",
"Skill",
"SkillDict",
"SkillOrDict",
"PromptOptimizerConfig",
"PromptOptimizerConfigDict",
"PromptOptimizerConfigOrDict",
Expand Down Expand Up @@ -2591,6 +2605,7 @@
"OptimizeTarget",
"MemoryMetadataMergeStrategy",
"GenerateMemoriesResponseGeneratedMemoryAction",
"SkillState",
"PromptOptimizerMethod",
"OptimizationMethod",
"PromptData",
Expand Down Expand Up @@ -2714,6 +2729,7 @@
"_CustomJobParameters",
"_GetCustomJobParameters",
"_OptimizeRequestParameters",
"_GetSkillRequestParameters",
"evals",
"agent_engines",
"prompts",
Expand Down
Loading
Loading