chore: import upstream snapshot with attribution
Continuous Integration / Pre-commit Linter (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.10) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.11) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.12) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.10) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.11) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.12) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.14) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Waiting to run
Copybara PR Handler / close-imported-pr (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:13 +08:00
commit ec2b666284
2231 changed files with 491535 additions and 0 deletions
@@ -0,0 +1,134 @@
# Pydantic Argument Sample Agent
This sample demonstrates the automatic Pydantic model conversion feature in ADK FunctionTool.
## What This Demonstrates
This sample shows two key features of the Pydantic argument conversion:
### 1. Optional Type Handling
The `create_full_user_account` function demonstrates `Optional[PydanticModel]` conversion:
Before the fix, Optional parameters required manual conversion:
```python
def create_full_user_account(
profile: UserProfile,
preferences: Optional[UserPreferences] = None
) -> dict:
# Manual conversion needed:
if not isinstance(profile, UserProfile):
profile = UserProfile.model_validate(profile)
if preferences is not None and not isinstance(preferences, UserPreferences):
preferences = UserPreferences.model_validate(preferences)
# Your function logic here...
```
**After the fix**, Union/Optional Pydantic models are handled automatically:
```python
def create_full_user_account(
profile: UserProfile,
preferences: Optional[UserPreferences] = None
) -> dict:
# Both profile and preferences are guaranteed to be proper instances!
# profile: UserProfile instance (converted from JSON)
# preferences: UserPreferences instance OR None (converted from JSON or kept as None)
return {"profile": profile.name, "theme": preferences.theme if preferences else "default"}
```
### 2. Union Type Handling
The `create_entity_profile` function demonstrates `Union[PydanticModel1, PydanticModel2]` conversion:
**Before the fix**, Union types required complex manual type checking:
```python
def create_entity_profile(entity: Union[UserProfile, CompanyProfile]) -> dict:
# Manual conversion needed:
if isinstance(entity, dict):
# Try to determine which model to use and convert manually
if 'company_name' in entity:
entity = CompanyProfile.model_validate(entity)
elif 'name' in entity:
entity = UserProfile.model_validate(entity)
else:
raise ValueError("Cannot determine entity type")
# Your function logic here...
```
**After the fix**, Union Pydantic models are handled automatically:
```python
def create_entity_profile(entity: Union[UserProfile, CompanyProfile]) -> dict:
# entity is guaranteed to be either UserProfile or CompanyProfile instance!
# The LLM sends appropriate JSON structure, and it gets converted
# to the correct Pydantic model based on JSON schema matching
if isinstance(entity, UserProfile):
return {"type": "user", "name": entity.name}
else: # CompanyProfile
return {"type": "company", "name": entity.company_name}
```
## How to Run
1. **Set up API credentials** (choose one):
**Option A: Google AI API**
```bash
export GOOGLE_GENAI_API_KEY="your-api-key"
```
**Option B: Vertex AI (requires Google Cloud project)**
```bash
export GOOGLE_CLOUD_PROJECT="your-project-id"
export GOOGLE_CLOUD_LOCATION="us-central1"
```
1. **Run the sample**:
```bash
cd contributing/samples
python -m pydantic_argument.main
```
## Expected Output
The agent will be prompted to create user profiles and accounts, demonstrating automatic Pydantic model conversion.
### Test Scenarios:
1. **Full Account with Preferences (Optional Type)**:
- **Input**: "Create an account for Alice, 25 years old, with dark theme and Spanish language preferences"
- **Tool Called**: `create_full_user_account(profile=UserProfile(...), preferences=UserPreferences(...))`
- **Conversion**: Two JSON dicts → `UserProfile` + `UserPreferences` instances
1. **Account with Different Preferences (Optional Type)**:
- **Input**: "Create a user account for Bob, age 30, with light theme, French language, and notifications disabled"
- **Tool Called**: `create_full_user_account(profile=UserProfile(...), preferences=UserPreferences(...))`
- **Conversion**: Two JSON dicts → `UserProfile` + `UserPreferences` instances
1. **Account with Default Preferences (Optional Type)**:
- **Input**: "Make an account for Charlie, 28 years old, but use default preferences"
- **Tool Called**: `create_full_user_account(profile=UserProfile(...), preferences=None)`
- **Conversion**: JSON dict → `UserProfile`, None → None (Optional handling)
1. **Company Profile Creation (Union Type)**:
- **Input**: "Create a profile for Tech Corp company, software industry, with 150 employees"
- **Tool Called**: `create_entity_profile(entity=CompanyProfile(...))`
- **Conversion**: JSON dict → `CompanyProfile` instance (Union type resolution)
1. **User Profile Creation (Union Type)**:
- **Input**: "Create an entity profile for Diana, 32 years old"
- **Tool Called**: `create_entity_profile(entity=UserProfile(...))`
- **Conversion**: JSON dict → `UserProfile` instance (Union type resolution)
@@ -0,0 +1,15 @@
# Copyright 2026 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.
from . import agent
@@ -0,0 +1,182 @@
# Copyright 2026 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.
"""Simple agent demonstrating Pydantic model arguments in tools."""
from typing import Optional
from typing import Union
from google.adk.agents.llm_agent import Agent
from google.adk.tools.function_tool import FunctionTool
import pydantic
class UserProfile(pydantic.BaseModel):
"""A user's profile information."""
name: str
age: int
email: Optional[str] = None
class UserPreferences(pydantic.BaseModel):
"""A user's preferences."""
theme: str = "light"
language: str = "English"
notifications_enabled: bool = True
class CompanyProfile(pydantic.BaseModel):
"""A company's profile information."""
company_name: str
industry: str
employee_count: int
website: Optional[str] = None
def create_full_user_account(
profile: UserProfile, preferences: Optional[UserPreferences] = None
) -> dict:
"""Create a complete user account with profile and optional preferences.
This function demonstrates Union/Optional Pydantic model handling.
The preferences parameter is Optional[UserPreferences], which is
internally Union[UserPreferences, None].
Before the fix, we would need:
if preferences is not None and not isinstance(preferences, UserPreferences):
preferences = UserPreferences.model_validate(preferences)
Now the FunctionTool automatically handles this conversion!
Args:
profile: The user's profile information (required)
preferences: Optional user preferences (Union[UserPreferences, None])
Returns:
A dictionary containing the complete user account.
"""
# Use default preferences if not provided
if preferences is None:
preferences = UserPreferences()
# Both profile and preferences are guaranteed to be proper Pydantic instances!
return {
"status": "account_created",
"message": f"Full account created for {profile.name}!",
"profile": {
"name": profile.name,
"age": profile.age,
"email": profile.email or "Not provided",
"profile_type": type(profile).__name__,
},
"preferences": {
"theme": preferences.theme,
"language": preferences.language,
"notifications_enabled": preferences.notifications_enabled,
"preferences_type": type(preferences).__name__,
},
"conversion_demo": {
"profile_converted": "JSON dict → UserProfile instance",
"preferences_converted": (
"JSON dict → UserPreferences instance"
if preferences
else "None → default UserPreferences"
),
},
}
def create_entity_profile(entity: Union[UserProfile, CompanyProfile]) -> dict:
"""Create a profile for either a user or a company.
This function demonstrates Union type handling with multiple Pydantic models.
The entity parameter accepts Union[UserProfile, CompanyProfile].
Before the fix, we would need complex type checking:
if isinstance(entity, dict):
# Try to determine which model to use and convert manually
if 'company_name' in entity:
entity = CompanyProfile.model_validate(entity)
elif 'name' in entity:
entity = UserProfile.model_validate(entity)
else:
raise ValueError("Cannot determine entity type")
Now the FunctionTool automatically handles Union type conversion!
The LLM will send the appropriate JSON structure, and it gets converted
to the correct Pydantic model based on the JSON schema matching.
Args:
entity: Either a UserProfile or CompanyProfile (Union type)
Returns:
A dictionary containing the entity profile information.
"""
if isinstance(entity, UserProfile):
return {
"status": "user_profile_created",
"entity_type": "user",
"message": f"User profile created for {entity.name}!",
"profile": {
"name": entity.name,
"age": entity.age,
"email": entity.email or "Not provided",
"model_type": type(entity).__name__,
},
}
elif isinstance(entity, CompanyProfile):
return {
"status": "company_profile_created",
"entity_type": "company",
"message": f"Company profile created for {entity.company_name}!",
"profile": {
"company_name": entity.company_name,
"industry": entity.industry,
"employee_count": entity.employee_count,
"website": entity.website or "Not provided",
"model_type": type(entity).__name__,
},
}
else:
return {
"status": "error",
"message": f"Unexpected entity type: {type(entity)}",
}
# Create the agent with all Pydantic tools
root_agent = Agent(
model="gemini-2.5-pro",
name="profile_agent",
description=(
"Helpful assistant that helps creating accounts and profiles for users"
" and companies"
),
instruction="""
You are a helpful assistant that can create accounts and profiles for users and companies.
When someone asks you to create a user account, use `create_full_user_account`.
When someone asks you to create a profile and it's unclear whether they mean a user or company, use `create_entity_profile`.
When someone specifically mentions a company, use `create_entity_profile`.
Use the tools with the structured data provided by the user.
""",
tools=[
FunctionTool(create_full_user_account),
FunctionTool(create_entity_profile),
],
)
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""Simple test script for Pydantic argument agent."""
# Copyright 2026 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.
import asyncio
import logging
from google.adk.agents.run_config import RunConfig
from google.adk.cli.utils import logs
from google.adk.runners import InMemoryRunner
from google.genai import types
from pydantic_argument import agent
APP_NAME = "pydantic_test_app"
USER_ID = "test_user"
logs.setup_adk_logger(level=logging.INFO)
async def call_agent_async(runner, user_id, session_id, prompt):
"""Helper function to call the agent and return response."""
content = types.Content(
role="user", parts=[types.Part.from_text(text=prompt)]
)
final_response_text = ""
async for event in runner.run_async(
user_id=user_id,
session_id=session_id,
new_message=content,
run_config=RunConfig(save_input_blobs_as_artifacts=False),
):
if hasattr(event, "content") and event.content:
final_response_text += event.content
return final_response_text
async def main():
print("🚀 Testing Pydantic Argument Feature")
print("=" * 50)
runner = InMemoryRunner(
agent=agent.root_agent,
app_name=APP_NAME,
)
# Create a session
session = await runner.session_service.create_session(
app_name=APP_NAME, user_id=USER_ID
)
test_prompts = [
# Test Optional[Pydantic] type handling (UserProfile + Optional[UserPreferences])
(
"Create an account for Alice, 25 years old, email: alice@example.com,"
" with dark theme and Spanish language preferences"
),
(
"Create a user account for Bob, age 30, no email, "
"with light theme, French language, and notifications disabled"
),
(
"Make an account for Charlie, 28 years old, email: charlie@test.com, "
"but use default preferences"
),
# Test Union type handling (Union[UserProfile, CompanyProfile])
(
"Create a profile for Tech Corp company, software industry, "
"with 150 employees and website techcorp.com"
),
(
"Create an entity profile for Diana, 32 years old, "
"email diana@example.com"
),
]
for i, prompt in enumerate(test_prompts, 1):
print(f"\n📝 Test {i}: {prompt}")
print("-" * 40)
try:
response = await call_agent_async(runner, USER_ID, session.id, prompt)
print(f"✅ Response: {response}")
except Exception as e:
print(f"❌ Error: {e}")
print("\n" + "=" * 50)
print("✨ Testing complete!")
print("🔧 Features demonstrated:")
print(" • JSON dict → Pydantic model conversion (UserProfile)")
print(" • Optional type handling (Optional[UserPreferences])")
print(" • Union type handling (Union[UserProfile, CompanyProfile])")
print(" • Automatic model validation and conversion")
print(" • No manual isinstance() checks needed!")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,96 @@
{
"events": [
{
"author": "user",
"content": {
"parts": [
{
"text": "Create a profile for company Acme Corp in tech industry with 50 employees."
}
],
"role": "user"
},
"id": "e-1",
"invocationId": "i-1",
"nodeInfo": {
"path": ""
}
},
{
"author": "profile_agent",
"content": {
"parts": [
{
"functionCall": {
"args": {
"entity": {
"company_name": "Acme Corp",
"employee_count": 50,
"industry": "tech"
}
},
"id": "fc-1",
"name": "create_entity_profile"
}
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-2",
"invocationId": "i-1",
"longRunningToolIds": [],
"nodeInfo": {
"path": "profile_agent@1"
}
},
{
"author": "profile_agent",
"content": {
"parts": [
{
"functionResponse": {
"id": "fc-1",
"name": "create_entity_profile",
"response": {
"entity_type": "company",
"message": "Company profile created for Acme Corp!",
"profile": {
"company_name": "Acme Corp",
"employee_count": 50,
"industry": "tech",
"model_type": "CompanyProfile",
"website": "Not provided"
},
"status": "company_profile_created"
}
}
}
],
"role": "user"
},
"id": "e-3",
"invocationId": "i-1",
"nodeInfo": {
"path": "profile_agent@1"
}
},
{
"author": "profile_agent",
"content": {
"parts": [
{
"text": "I have created a profile for Acme Corp in the tech industry with 50 employees."
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-4",
"invocationId": "i-1",
"nodeInfo": {
"path": "profile_agent@1"
}
}
]
}
@@ -0,0 +1,104 @@
{
"events": [
{
"author": "user",
"content": {
"parts": [
{
"text": "Create a user account for Alice, age 30, email alice@example.com."
}
],
"role": "user"
},
"id": "e-1",
"invocationId": "i-1",
"nodeInfo": {
"path": ""
}
},
{
"author": "profile_agent",
"content": {
"parts": [
{
"functionCall": {
"args": {
"profile": {
"age": 30,
"email": "alice@example.com",
"name": "Alice"
}
},
"id": "fc-1",
"name": "create_full_user_account"
}
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-2",
"invocationId": "i-1",
"longRunningToolIds": [],
"nodeInfo": {
"path": "profile_agent@1"
}
},
{
"author": "profile_agent",
"content": {
"parts": [
{
"functionResponse": {
"id": "fc-1",
"name": "create_full_user_account",
"response": {
"conversion_demo": {
"preferences_converted": "JSON dict \u2192 UserPreferences instance",
"profile_converted": "JSON dict \u2192 UserProfile instance"
},
"message": "Full account created for Alice!",
"preferences": {
"language": "English",
"notifications_enabled": true,
"preferences_type": "UserPreferences",
"theme": "light"
},
"profile": {
"age": 30,
"email": "alice@example.com",
"name": "Alice",
"profile_type": "UserProfile"
},
"status": "account_created"
}
}
}
],
"role": "user"
},
"id": "e-3",
"invocationId": "i-1",
"nodeInfo": {
"path": "profile_agent@1"
}
},
{
"author": "profile_agent",
"content": {
"parts": [
{
"text": "I have created a user account for Alice, age 30, with the email alice@example.com."
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-4",
"invocationId": "i-1",
"nodeInfo": {
"path": "profile_agent@1"
}
}
]
}
@@ -0,0 +1,107 @@
{
"events": [
{
"author": "user",
"content": {
"parts": [
{
"text": "Create a user account for Bob, age 25, with dark theme and French language."
}
],
"role": "user"
},
"id": "e-1",
"invocationId": "i-1",
"nodeInfo": {
"path": ""
}
},
{
"author": "profile_agent",
"content": {
"parts": [
{
"functionCall": {
"args": {
"preferences": {
"language": "French",
"theme": "dark"
},
"profile": {
"age": 25,
"name": "Bob"
}
},
"id": "fc-1",
"name": "create_full_user_account"
}
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-2",
"invocationId": "i-1",
"longRunningToolIds": [],
"nodeInfo": {
"path": "profile_agent@1"
}
},
{
"author": "profile_agent",
"content": {
"parts": [
{
"functionResponse": {
"id": "fc-1",
"name": "create_full_user_account",
"response": {
"conversion_demo": {
"preferences_converted": "JSON dict \u2192 UserPreferences instance",
"profile_converted": "JSON dict \u2192 UserProfile instance"
},
"message": "Full account created for Bob!",
"preferences": {
"language": "French",
"notifications_enabled": true,
"preferences_type": "UserPreferences",
"theme": "dark"
},
"profile": {
"age": 25,
"email": "Not provided",
"name": "Bob",
"profile_type": "UserProfile"
},
"status": "account_created"
}
}
}
],
"role": "user"
},
"id": "e-3",
"invocationId": "i-1",
"nodeInfo": {
"path": "profile_agent@1"
}
},
{
"author": "profile_agent",
"content": {
"parts": [
{
"text": "OK. I have created a user account for Bob, age 25, with a dark theme and French language preference."
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-4",
"invocationId": "i-1",
"nodeInfo": {
"path": "profile_agent@1"
}
}
]
}