Skip to content

Commit 29fcd80

Browse files
authored
Merge branch 'main' into update-linter-for-hardcoded
2 parents c170c23 + 104edc8 commit 29fcd80

36 files changed

Lines changed: 1283 additions & 182 deletions

File tree

.github/workflows/copybara-pr-handler.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,19 @@ jobs:
5858
console.log(`Committer: ${committer}`);
5959
6060
// Check if this is a Copybara commit
61-
if (committer !== 'Copybara-Service') {
61+
const isCopybara = committer === 'Copybara-Service' ||
62+
commit.author?.email === 'genai-sdk-bot@google.com' ||
63+
message.includes('GitOrigin-RevId:') ||
64+
message.includes('PiperOrigin-RevId:');
65+
66+
if (!isCopybara) {
6267
console.log('Not a Copybara commit, skipping');
6368
continue;
6469
}
6570
6671
// Extract PR number from commit message
67-
// Pattern: "Merge https://github.com/google/adk-python/pull/3333"
68-
const prMatch = message.match(/Merge https:\/\/github\.com\/google\/adk-python\/pull\/(\d+)/);
72+
// Pattern matches both "Merge https://..." and "Merge: https://..."
73+
const prMatch = message.match(/Merge:?\s+https:\/\/github\.com\/google\/adk-python\/pull\/(\d+)/);
6974
7075
if (!prMatch) {
7176
console.log('No PR number found in Copybara commit message');

.github/workflows/pre-commit.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ name: Pre-commit Checks
1616

1717
on:
1818
push:
19-
branches: [main, v2]
19+
branches: [main, v1, v2]
2020
paths:
2121
- '**.py'
2222
- '.pre-commit-config.yaml'
2323
- 'pyproject.toml'
2424
pull_request:
25-
branches: [main, v2]
25+
branches: [main, v1, v2]
2626
paths:
2727
- '**.py'
2828
- '.pre-commit-config.yaml'

.github/workflows/python-unit-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: Python Unit Tests
22

33
on:
44
push:
5-
branches: [ main ]
5+
branches: [main, v1, v2]
66
pull_request:
7-
branches: [ main, v2 ]
7+
branches: [main, v1, v2]
88

99
permissions:
1010
contents: read

contributing/samples/integrations/data_agent/agent.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,22 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from __future__ import annotations
16+
17+
import asyncio
1518
import os
19+
from typing import Any
1620

1721
from google.adk.agents import Agent
1822
from google.adk.auth.auth_credential import AuthCredentialTypes
23+
from google.adk.tools import load_artifacts
1924
from google.adk.tools.data_agent.config import DataAgentToolConfig
2025
from google.adk.tools.data_agent.credentials import DataAgentCredentialsConfig
2126
from google.adk.tools.data_agent.data_agent_toolset import DataAgentToolset
27+
from google.adk.tools.tool_context import ToolContext
2228
import google.auth
2329
import google.auth.transport.requests
30+
from google.genai import types
2431

2532
# Define the desired credential type.
2633
# By default use Application Default Credentials (ADC) from the local
@@ -72,16 +79,62 @@
7279
],
7380
)
7481

82+
# NOTE: The generate_chart tool requires 'altair' and 'vl-convert-python' to be
83+
# installed in your environment. You can install them using:
84+
# pip install altair vl-convert-python
85+
async def generate_chart(
86+
chart_spec: dict[str, Any], tool_context: ToolContext
87+
) -> dict[str, str]:
88+
"""Generates a professional chart using Altair based on a Vega-Lite spec.
89+
90+
Args:
91+
chart_spec: A dictionary defining a Vega-Lite chart.
92+
tool_context: The tool context.
93+
94+
Returns:
95+
A dictionary containing the status of the chart generation ("success" or
96+
"error"), a detail message, and the filename if successful.
97+
"""
98+
import altair as alt
99+
import vl_convert as vlc
100+
101+
try:
102+
# Altair can take a Vega-Lite dict directly and render it.
103+
# We use vl-convert to transform the spec into a high-quality PNG.
104+
png_data = await asyncio.to_thread(vlc.vegalite_to_png, chart_spec, scale=2)
105+
106+
# Save as artifact
107+
await tool_context.save_artifact(
108+
"chart.png",
109+
types.Part.from_bytes(data=png_data, mime_type="image/png"),
110+
)
111+
title = chart_spec.get("title", "Chart")
112+
return {
113+
"status": "success",
114+
"detail": (
115+
f"Professional chart '{title}' rendered using Altair/Vega-Lite."
116+
),
117+
"filename": "chart.png",
118+
}
119+
except Exception as e: # pylint: disable=broad-exception-caught
120+
return {"status": "error", "detail": f"Failed to render chart: {str(e)}"}
121+
122+
75123
root_agent = Agent(
76124
name="data_agent",
77-
description="Agent to answer user questions using Data Agents.",
125+
description=(
126+
"Agent to answer user questions using Data Agents and generate charts."
127+
),
78128
instruction=(
79129
"## Persona\nYou are a helpful assistant that uses Data Agents"
80130
" to answer user questions about their data.\n\n## Tools\n- You can"
81131
" list available data agents using `list_accessible_data_agents`.\n-"
82132
" You can get information about a specific data agent using"
83133
" `get_data_agent_info`.\n- You can chat with a specific data"
84-
" agent using `ask_data_agent`.\n"
134+
" agent using `ask_data_agent`.\n- `generate_chart` renders"
135+
" professional charts from a `chart_spec` (Vega-Lite JSON). Use this"
136+
" whenever you need to visualize data; do not show raw JSON to the"
137+
" user.\n- You can load artifacts using `load_artifacts`.\n"
85138
),
86-
tools=[da_toolset],
139+
tools=[da_toolset, generate_chart, load_artifacts],
87140
)

contributing/samples/integrations/gepa/OWNERS

Lines changed: 0 additions & 3 deletions
This file was deleted.

contributing/samples/integrations/sandbox_computer_use/agent.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
- GOOGLE_CLOUD_PROJECT: Your GCP project ID
2525
- VMAAS_SERVICE_ACCOUNT: Your service account email
2626
- VMAAS_SANDBOX_NAME: (Optional) Existing sandbox resource name for BYOS mode
27+
- VMAAS_SANDBOX_TEMPLATE_NAME: (Optional) Sandbox template name to create a new sandbox (mutually exclusive with VMAAS_SANDBOX_NAME)
28+
- VMAAS_SANDBOX_SNAPSHOT_NAME: (Optional) Sandbox snapshot name to create a new sandbox (mutually exclusive with VMAAS_SANDBOX_NAME)
2729
2830
Usage:
2931
# Run via ADK web UI
@@ -53,12 +55,17 @@
5355
SANDBOX_NAME = os.environ.get("SANDBOX_NAME") or os.environ.get(
5456
"VMAAS_SANDBOX_NAME"
5557
)
58+
SANDBOX_TEMPLATE_NAME = os.environ.get("VMAAS_SANDBOX_TEMPLATE_NAME")
59+
SANDBOX_SNAPSHOT_NAME = os.environ.get("VMAAS_SANDBOX_SNAPSHOT_NAME")
60+
5661

5762
# Create the sandbox computer
5863
sandbox_computer = AgentEngineSandboxComputer(
5964
project_id=PROJECT_ID,
6065
service_account_email=SERVICE_ACCOUNT,
6166
sandbox_name=SANDBOX_NAME,
67+
sandbox_template_name=SANDBOX_TEMPLATE_NAME,
68+
sandbox_snapshot_name=SANDBOX_SNAPSHOT_NAME,
6269
search_engine_url="https://www.google.com",
6370
)
6471

contributing/samples/integrations/sandbox_computer_use/main.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
- GOOGLE_CLOUD_PROJECT: Your GCP project ID
2323
- VMAAS_SERVICE_ACCOUNT: Your service account email with
2424
roles/iam.serviceAccountTokenCreator permission
25+
- VMAAS_SANDBOX_NAME: (Optional) Existing sandbox resource name for BYOS mode
26+
- VMAAS_SANDBOX_TEMPLATE_NAME: (Optional) Sandbox template name to create a new sandbox (mutually exclusive with VMAAS_SANDBOX_NAME)
27+
- VMAAS_SANDBOX_SNAPSHOT_NAME: (Optional) Sandbox snapshot name to create a new sandbox (mutually exclusive with VMAAS_SANDBOX_NAME)
2528
2629
Usage:
2730
cd contributing/samples

contributing/samples/tools/pydantic_argument/tests/test_create_company.json

Lines changed: 11 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -53,66 +53,23 @@
5353
"id": "fc-1",
5454
"name": "create_entity_profile",
5555
"response": {
56-
"message": "Unexpected entity type: <class 'dict'>",
57-
"status": "error"
58-
}
59-
}
60-
}
61-
],
62-
"role": "user"
63-
},
64-
"id": "e-3",
65-
"invocationId": "i-1",
66-
"nodeInfo": {
67-
"path": "profile_agent@1"
68-
}
69-
},
70-
{
71-
"author": "profile_agent",
72-
"content": {
73-
"parts": [
74-
{
75-
"functionCall": {
76-
"args": {
77-
"entity": {
56+
"entity_type": "company",
57+
"message": "Company profile created for Acme Corp!",
58+
"profile": {
7859
"company_name": "Acme Corp",
7960
"employee_count": 50,
80-
"industry": "tech"
81-
}
82-
},
83-
"id": "fc-2",
84-
"name": "create_entity_profile"
85-
}
86-
}
87-
],
88-
"role": "model"
89-
},
90-
"finishReason": "STOP",
91-
"id": "e-4",
92-
"invocationId": "i-1",
93-
"longRunningToolIds": [],
94-
"nodeInfo": {
95-
"path": "profile_agent@1"
96-
}
97-
},
98-
{
99-
"author": "profile_agent",
100-
"content": {
101-
"parts": [
102-
{
103-
"functionResponse": {
104-
"id": "fc-2",
105-
"name": "create_entity_profile",
106-
"response": {
107-
"message": "Unexpected entity type: <class 'dict'>",
108-
"status": "error"
61+
"industry": "tech",
62+
"model_type": "CompanyProfile",
63+
"website": "Not provided"
64+
},
65+
"status": "company_profile_created"
10966
}
11067
}
11168
}
11269
],
11370
"role": "user"
11471
},
115-
"id": "e-5",
72+
"id": "e-3",
11673
"invocationId": "i-1",
11774
"nodeInfo": {
11875
"path": "profile_agent@1"
@@ -123,13 +80,13 @@
12380
"content": {
12481
"parts": [
12582
{
126-
"text": "I apologize for the repeated errors. It seems I am having trouble with the tool's expected input format. I will try again to create the company profile for Acme Corp. I believe the issue was in how I was structuring the data for the `create_entity_profile` function. I will now use the correct dataclass constructor to create the profile. Please bear with me."
83+
"text": "I have created a profile for Acme Corp in the tech industry with 50 employees."
12784
}
12885
],
12986
"role": "model"
13087
},
13188
"finishReason": "STOP",
132-
"id": "e-6",
89+
"id": "e-4",
13390
"invocationId": "i-1",
13491
"nodeInfo": {
13592
"path": "profile_agent@1"

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ dependencies = [
4747
"packaging>=21",
4848
"pydantic>=2.12,<3",
4949
"python-dotenv>=1,<2",
50+
"python-multipart>=0.0.9,<1",
5051
# go/keep-sorted start
5152
"pyyaml>=6.0.2,<7",
5253
"requests>=2.32.4,<3",

src/google/adk/cli/utils/evals.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,20 @@
1515
from __future__ import annotations
1616

1717
import os
18+
from typing import Any, TYPE_CHECKING
1819

1920
from pydantic import alias_generators
2021
from pydantic import BaseModel
2122
from pydantic import ConfigDict
2223

2324
from ...evaluation.eval_case import Invocation
2425
from ...evaluation.evaluation_generator import EvaluationGenerator
25-
from ...evaluation.gcs_eval_set_results_manager import GcsEvalSetResultsManager
26-
from ...evaluation.gcs_eval_sets_manager import GcsEvalSetsManager
2726
from ...sessions.session import Session
2827

28+
if TYPE_CHECKING:
29+
from ...evaluation.gcs_eval_set_results_manager import GcsEvalSetResultsManager
30+
from ...evaluation.gcs_eval_sets_manager import GcsEvalSetsManager
31+
2932

3033
class GcsEvalManagers(BaseModel):
3134
model_config = ConfigDict(
@@ -34,9 +37,9 @@ class GcsEvalManagers(BaseModel):
3437
arbitrary_types_allowed=True,
3538
)
3639

37-
eval_sets_manager: GcsEvalSetsManager
40+
eval_sets_manager: 'GcsEvalSetsManager'
3841

39-
eval_set_results_manager: GcsEvalSetResultsManager
42+
eval_set_results_manager: 'GcsEvalSetResultsManager'
4043

4144

4245
def convert_session_to_eval_invocations(session: Session) -> list[Invocation]:
@@ -66,8 +69,19 @@ def create_gcs_eval_managers_from_uri(
6669
6770
Raises:
6871
ValueError: If the eval_storage_uri is not supported.
72+
RuntimeError: If GCP optional dependencies are missing.
6973
"""
7074
if eval_storage_uri.startswith('gs://'):
75+
try:
76+
from ...evaluation.gcs_eval_set_results_manager import GcsEvalSetResultsManager
77+
from ...evaluation.gcs_eval_sets_manager import GcsEvalSetsManager
78+
except ImportError as e:
79+
raise RuntimeError(
80+
'GCS evaluation managers require Google Cloud optional dependencies.\n'
81+
'Please install them using: pip install google-adk[gcp]\n'
82+
'Or: pip install google-cloud-storage>=2.18'
83+
) from e
84+
7185
gcs_bucket = eval_storage_uri.split('://')[1]
7286
eval_sets_manager = GcsEvalSetsManager(
7387
bucket_name=gcs_bucket, project=os.environ['GOOGLE_CLOUD_PROJECT']

0 commit comments

Comments
 (0)