Files
wehub-resource-sync ec2b666284
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:25:13 +08:00

309 lines
8.5 KiB
Python

# 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.
"""Tools for Vote Taker Agent."""
from datetime import datetime
import os
from typing import Any
from typing import Dict
from typing import Optional
from google.adk.tools import ToolContext
from google.cloud import bigquery
# Configuration
GOOGLE_CLOUD_PROJECT = os.getenv("GOOGLE_CLOUD_PROJECT", "")
BQ_DATASET = os.getenv("BQ_DATASET", "")
BQ_VOTES_TABLE = os.getenv("BQ_VOTES_TABLE", "")
LOCAL_MODE = os.getenv("LOCAL_MODE", "true").lower() == "true"
# In-memory storage for local development
local_votes = []
# Voting options for multiple rounds
VOTING_ROUNDS = {
"round1": {
"question": "What would you like to see next?",
"options": {
"A": {
"title": "Computer Use",
"description": "Autonomous browser control with Gemini 2.5",
},
"B": {
"title": "A2A Multi-Agent",
"description": "Agent-to-Agent coordination patterns",
},
"C": {
"title": "Production Observability",
"description": "Monitoring and debugging at scale",
},
},
},
"round2": {
"question": "What shall we add to this image now?",
"options": {
"A": {
"title": "Add butterflies",
"description": "Add colorful butterflies around the dog",
},
"B": {
"title": "Add a rainbow",
"description": "Add a vibrant rainbow in the sky",
},
"C": {
"title": "Add flowers",
"description": "Add blooming flowers in the grass",
},
},
},
}
# Default to round 1 options for backward compatibility
VOTING_OPTIONS = VOTING_ROUNDS["round1"]["options"]
CURRENT_ROUND = "round1"
def get_voting_options(
tool_context: ToolContext, round_id: Optional[str] = None
) -> Dict[str, Any]:
"""Returns the current voting options available to the user.
Args:
tool_context: ADK tool context
round_id: Optional round ID (round1, round2, etc.)
Returns:
dict: Voting options with titles and descriptions
"""
print(f"Tool called: get_voting_options - round={round_id or CURRENT_ROUND}")
active_round = round_id or CURRENT_ROUND
if active_round not in VOTING_ROUNDS:
return {"success": False, "error": f"Invalid round ID: {active_round}"}
round_data = VOTING_ROUNDS[active_round]
return {
"success": True,
"round": active_round,
"question": round_data["question"],
"image_url": round_data.get("image_url"),
"options": round_data["options"],
"message": round_data["question"],
}
def set_voting_round(
round_id: str, tool_context: ToolContext
) -> Dict[str, Any]:
"""Sets the current voting round.
Args:
round_id: The round ID to set (round1, round2, etc.)
tool_context: ADK tool context
Returns:
dict: Confirmation with new round details
"""
global CURRENT_ROUND, VOTING_OPTIONS
print(f"Tool called: set_voting_round - round={round_id}")
if round_id not in VOTING_ROUNDS:
return {"success": False, "error": f"Invalid round ID: {round_id}"}
CURRENT_ROUND = round_id
VOTING_OPTIONS = VOTING_ROUNDS[round_id]["options"]
return {
"success": True,
"round": round_id,
"question": VOTING_ROUNDS[round_id]["question"],
"message": f"Voting round changed to: {round_id}",
}
def store_vote_to_bigquery(
vote_choice: str,
user_id: str,
additional_feedback: Optional[str],
tool_context: ToolContext,
round_id: Optional[str] = None,
) -> Dict[str, Any]:
"""Stores a validated vote to BigQuery (or local storage in dev mode).
Args:
vote_choice: The vote option (A, B, or C)
user_id: Unique identifier for the voter
additional_feedback: Optional feedback from the user
tool_context: ADK tool context
round_id: Optional round ID for the vote
Returns:
dict: Confirmation with vote details
"""
print(
f"Tool called: store_vote_to_bigquery - vote={vote_choice},"
f" user={user_id}, round={round_id or CURRENT_ROUND}"
)
active_round = round_id or CURRENT_ROUND
active_options = VOTING_ROUNDS[active_round]["options"]
# Validate vote choice
vote = vote_choice.upper()
if vote not in active_options:
return {
"success": False,
"error": "Invalid vote choice. Must be A, B, or C.",
"vote": vote,
}
# Create vote record
vote_record = {
"vote": vote,
"user_id": user_id,
"additional_feedback": additional_feedback or "",
"timestamp": datetime.utcnow().isoformat(),
"round": active_round,
"option_title": active_options[vote]["title"],
}
if LOCAL_MODE:
# Store locally for development
local_votes.append(vote_record)
return {
"success": True,
"message": (
f"✅ Vote recorded for Option {vote}:"
f" {active_options[vote]['title']}!"
),
"vote_details": vote_record,
"total_votes": len(local_votes),
}
else:
# Store to BigQuery for production
try:
client = bigquery.Client(project=GOOGLE_CLOUD_PROJECT)
table_id = f"{GOOGLE_CLOUD_PROJECT}.{BQ_DATASET}.{BQ_VOTES_TABLE}"
errors = client.insert_rows_json(table_id, [vote_record])
if errors:
return {
"success": False,
"error": "Failed to store vote to database",
"details": str(errors),
}
return {
"success": True,
"message": (
f"✅ Vote recorded for Option {vote}:"
f" {active_options[vote]['title']}!"
),
"vote_details": vote_record,
}
except Exception as e:
return {
"success": False,
"error": "Database error occurred",
"details": str(e),
}
def get_vote_summary(tool_context: ToolContext) -> Dict[str, Any]:
"""Returns a summary of all votes collected so far.
Returns:
dict: Vote counts and summary statistics
"""
print("Tool called: get_vote_summary")
if LOCAL_MODE:
# Calculate summary from local storage
vote_counts = {"A": 0, "B": 0, "C": 0}
for vote_record in local_votes:
vote = vote_record.get("vote")
if vote in vote_counts:
vote_counts[vote] += 1
total_votes = len(local_votes)
# Determine winner
winner = None
if total_votes > 0:
winner = max(vote_counts, key=vote_counts.get)
return {
"success": True,
"total_votes": total_votes,
"breakdown": vote_counts,
"winner": winner,
"winner_title": VOTING_OPTIONS[winner]["title"] if winner else None,
"message": (
f"Total votes: {total_votes}. Leading option: {winner}"
if winner
else "No votes yet."
),
}
else:
# Query BigQuery for production
try:
client = bigquery.Client(project=GOOGLE_CLOUD_PROJECT)
query = f"""
SELECT
vote,
COUNT(*) as count
FROM `{GOOGLE_CLOUD_PROJECT}.{BQ_DATASET}.{BQ_VOTES_TABLE}`
GROUP BY vote
ORDER BY count DESC
"""
results = client.query(query).result()
vote_counts = {"A": 0, "B": 0, "C": 0}
for row in results:
vote_counts[row.vote] = row.count
total_votes = sum(vote_counts.values())
winner = (
max(vote_counts, key=vote_counts.get) if total_votes > 0 else None
)
return {
"success": True,
"total_votes": total_votes,
"breakdown": vote_counts,
"winner": winner,
"winner_title": VOTING_OPTIONS[winner]["title"] if winner else None,
"message": (
f"Total votes: {total_votes}. Leading option: {winner}"
if winner
else "No votes yet."
),
}
except Exception as e:
return {
"success": False,
"error": "Failed to retrieve vote summary",
"details": str(e),
}