You've already forked adk-python
mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a1115c504 | |||
| 913d771d6d | |||
| 58e07cae83 | |||
| 157d9be88d | |||
| 18a541c8fa | |||
| dcea7767c6 | |||
| 2c739ab581 | |||
| 9a207cb832 | |||
| a17ebe6ebd | |||
| 55201cb6a1 | |||
| 0a9625317a | |||
| f9fa7841df | |||
| 5f89a469ec | |||
| 6d174eba30 | |||
| c04adaade1 | |||
| 1ae176ad2f | |||
| 694b71256c | |||
| e1812797ad | |||
| c755cf23c5 | |||
| 94caccc148 | |||
| 476805d5b9 | |||
| e2a81365ec | |||
| 28dfcd2512 | |||
| a6b1baa61b | |||
| e384fa4ad7 | |||
| aafa80bd85 | |||
| 4bda245171 | |||
| 31b81a342d | |||
| fef8778429 | |||
| fe1d5aa439 | |||
| 8201f9aebd | |||
| 1cfc555e70 | |||
| badbcbd7a4 | |||
| 675faefc67 | |||
| a4d432a9e6 | |||
| d1bda9d946 | |||
| a19d617ed8 | |||
| 8ebf229c47 | |||
| b51a1f45fd | |||
| 8932106246 | |||
| 233fd20243 | |||
| d129fd636b | |||
| 131957c531 | |||
| 8e285874da | |||
| cb55970969 | |||
| 40b15ad278 | |||
| dbdeb49090 | |||
| 177980106b | |||
| c5b063f1ff | |||
| 313f1b0913 | |||
| b2fc7740b3 | |||
| 29e4ca9152 | |||
| 4ccda99e8e | |||
| d22920bd7f | |||
| 2ff9b1f639 | |||
| 0a5cf45a75 | |||
| 1551bd4f4d | |||
| bbceb4f2e8 | |||
| b08bdbcd7f | |||
| fc65873d7c | |||
| a7ea374dfb |
@@ -43,11 +43,19 @@ jobs:
|
||||
run: |
|
||||
uv venv .venv
|
||||
source .venv/bin/activate
|
||||
uv sync --extra test --extra eval
|
||||
uv sync --extra test --extra eval --extra a2a
|
||||
|
||||
- name: Run unit tests with pytest
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
pytest tests/unittests \
|
||||
--ignore=tests/unittests/artifacts/test_artifact_service.py \
|
||||
--ignore=tests/unittests/tools/google_api_tool/test_googleapi_to_openapi_converter.py
|
||||
if [[ "${{ matrix.python-version }}" == "3.9" ]]; then
|
||||
pytest tests/unittests \
|
||||
--ignore=tests/unittests/a2a \
|
||||
--ignore=tests/unittests/tools/mcp_tool \
|
||||
--ignore=tests/unittests/artifacts/test_artifact_service.py \
|
||||
--ignore=tests/unittests/tools/google_api_tool/test_googleapi_to_openapi_converter.py
|
||||
else
|
||||
pytest tests/unittests \
|
||||
--ignore=tests/unittests/artifacts/test_artifact_service.py \
|
||||
--ignore=tests/unittests/tools/google_api_tool/test_googleapi_to_openapi_converter.py
|
||||
fi
|
||||
@@ -0,0 +1,43 @@
|
||||
name: ADK Issue Triaging Agent
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, reopened]
|
||||
schedule:
|
||||
- cron: '0 */6 * * *' # every 6h
|
||||
|
||||
jobs:
|
||||
agent-triage-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests google-adk
|
||||
|
||||
- name: Run Triaging Script
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ADK_TRIAGE_AGENT }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
GOOGLE_GENAI_USE_VERTEXAI: 0
|
||||
OWNER: 'google'
|
||||
REPO: 'adk-python'
|
||||
INTERACTIVE: 0
|
||||
EVENT_NAME: ${{ github.event_name }} # 'issues', 'schedule', etc.
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
ISSUE_TITLE: ${{ github.event.issue.title }}
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
ISSUE_COUNT_TO_PROCESS: '3' # Process 3 issues at a time on schedule
|
||||
run: python contributing/samples/adk_triaging_agent/main.py
|
||||
@@ -0,0 +1,67 @@
|
||||
# ADK Issue Triaging Assistant
|
||||
|
||||
The ADK Issue Triaging Assistant is a Python-based agent designed to help manage and triage GitHub issues for the `google/adk-python` repository. It uses a large language model to analyze new and unlabelled issues, recommend appropriate labels based on a predefined set of rules, and apply them.
|
||||
|
||||
This agent can be operated in two distinct modes: an interactive mode for local use or as a fully automated GitHub Actions workflow.
|
||||
|
||||
---
|
||||
|
||||
## Interactive Mode
|
||||
|
||||
This mode allows you to run the agent locally to review its recommendations in real-time before any changes are made to your repository's issues.
|
||||
|
||||
### Features
|
||||
* **Web Interface**: The agent's interactive mode can be rendered in a web browser using the ADK's `adk web` command.
|
||||
* **User Approval**: In interactive mode, the agent is instructed to ask for your confirmation before applying a label to a GitHub issue.
|
||||
|
||||
### Running in Interactive Mode
|
||||
To run the agent in interactive mode, first set the required environment variables. Then, execute the following command in your terminal:
|
||||
|
||||
```bash
|
||||
adk web
|
||||
```
|
||||
This will start a local server and provide a URL to access the agent's web interface in your browser.
|
||||
|
||||
---
|
||||
|
||||
## GitHub Workflow Mode
|
||||
|
||||
For automated, hands-off issue triaging, the agent can be integrated directly into your repository's CI/CD pipeline using a GitHub Actions workflow.
|
||||
|
||||
### Workflow Triggers
|
||||
The GitHub workflow is configured to run on specific triggers:
|
||||
|
||||
1. **Issue Events**: The workflow executes automatically whenever a new issue is `opened` or an existing one is `reopened`.
|
||||
|
||||
2. **Scheduled Runs**: The workflow also runs on a recurring schedule (every 6 hours) to process any unlabelled issues that may have been missed.
|
||||
|
||||
### Automated Labeling
|
||||
When running as part of the GitHub workflow, the agent operates non-interactively. It identifies the best label and applies it directly without requiring user approval. This behavior is configured by setting the `INTERACTIVE` environment variable to `0` in the workflow file.
|
||||
|
||||
### Workflow Configuration
|
||||
The workflow is defined in a YAML file (`.github/workflows/triage.yml`). This file contains the steps to check out the code, set up the Python environment, install dependencies, and run the triaging script with the necessary environment variables and secrets.
|
||||
|
||||
---
|
||||
|
||||
## Setup and Configuration
|
||||
|
||||
Whether running in interactive or workflow mode, the agent requires the following setup.
|
||||
|
||||
### Dependencies
|
||||
The agent requires the following Python libraries.
|
||||
|
||||
```bash
|
||||
pip install --upgrade pip
|
||||
pip install google-adk requests
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
The following environment variables are required for the agent to connect to the necessary services.
|
||||
|
||||
* `GITHUB_TOKEN`: **(Required)** A GitHub Personal Access Token with `issues:write` permissions. Needed for both interactive and workflow modes.
|
||||
* `GOOGLE_API_KEY`: **(Required)** Your API key for the Gemini API. Needed for both interactive and workflow modes.
|
||||
* `OWNER`: The GitHub organization or username that owns the repository (e.g., `google`). Needed for both modes.
|
||||
* `REPO`: The name of the GitHub repository (e.g., `adk-python`). Needed for both modes.
|
||||
* `INTERACTIVE`: Controls the agent's interaction mode. For the automated workflow, this is set to `0`. For interactive mode, it should be set to `1` or left unset.
|
||||
|
||||
For local execution in interactive mode, you can place these variables in a `.env` file in the project's root directory. For the GitHub workflow, they should be configured as repository secrets.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# 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.
|
||||
|
||||
from . import agent
|
||||
@@ -13,61 +13,73 @@
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
|
||||
from google.adk import Agent
|
||||
from google.adk.tools.tool_context import ToolContext
|
||||
from google.genai import types
|
||||
import requests
|
||||
|
||||
# Read the PAT from the environment variable
|
||||
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") # Ensure you've set this in your shell
|
||||
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
|
||||
if not GITHUB_TOKEN:
|
||||
raise ValueError("GITHUB_TOKEN environment variable not set")
|
||||
|
||||
# Repository information
|
||||
OWNER = "google"
|
||||
REPO = "adk-python"
|
||||
OWNER = os.getenv("OWNER", "google")
|
||||
REPO = os.getenv("REPO", "adk-python")
|
||||
BOT_LABEL = os.getenv("BOT_LABEL", "bot_triaged")
|
||||
|
||||
# Base URL for the GitHub API
|
||||
BASE_URL = "https://api.github.com"
|
||||
|
||||
# Headers including the Authorization header
|
||||
headers = {
|
||||
"Authorization": f"token {GITHUB_TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
}
|
||||
|
||||
ALLOWED_LABELS = [
|
||||
"documentation",
|
||||
"services",
|
||||
"question",
|
||||
"tools",
|
||||
"eval",
|
||||
"live",
|
||||
"models",
|
||||
"tracing",
|
||||
"core",
|
||||
"web",
|
||||
]
|
||||
|
||||
def list_issues(per_page: int):
|
||||
|
||||
def is_interactive():
|
||||
return os.environ.get("INTERACTIVE", "1").lower() in ["true", "1"]
|
||||
|
||||
|
||||
def list_issues(issue_count: int):
|
||||
"""
|
||||
Generator to list all issues for the repository by handling pagination.
|
||||
|
||||
Args:
|
||||
per_page: number of pages to return per page.
|
||||
issue_count: number of issues to return
|
||||
|
||||
"""
|
||||
state = "open"
|
||||
# only process the 1st page for testing for now
|
||||
page = 1
|
||||
results = []
|
||||
url = ( # :contentReference[oaicite:16]{index=16}
|
||||
f"{BASE_URL}/repos/{OWNER}/{REPO}/issues"
|
||||
)
|
||||
# Warning: let's only handle max 10 issues at a time to avoid bad results
|
||||
params = {"state": state, "per_page": per_page, "page": page}
|
||||
response = requests.get(url, headers=headers, params=params)
|
||||
response.raise_for_status() # :contentReference[oaicite:17]{index=17}
|
||||
issues = response.json()
|
||||
query = f"repo:{OWNER}/{REPO} is:open is:issue no:label"
|
||||
|
||||
unlabelled_issues = []
|
||||
url = f"{BASE_URL}/search/issues"
|
||||
|
||||
params = {
|
||||
"q": query,
|
||||
"sort": "created",
|
||||
"order": "desc",
|
||||
"per_page": issue_count,
|
||||
"page": 1,
|
||||
}
|
||||
response = requests.get(url, headers=headers, params=params, timeout=60)
|
||||
response.raise_for_status()
|
||||
json_response = response.json()
|
||||
issues = json_response.get("items", None)
|
||||
if not issues:
|
||||
return []
|
||||
for issue in issues:
|
||||
# Skip pull requests (issues API returns PRs as well)
|
||||
if "pull_request" in issue:
|
||||
continue
|
||||
results.append(issue)
|
||||
return results
|
||||
if not issue.get("labels", None) or len(issue["labels"]) == 0:
|
||||
unlabelled_issues.append(issue)
|
||||
return unlabelled_issues
|
||||
|
||||
|
||||
def add_label_to_issue(issue_number: str, label: str):
|
||||
@@ -78,41 +90,56 @@ def add_label_to_issue(issue_number: str, label: str):
|
||||
issue_number: issue number of the Github issue, in string foramt.
|
||||
label: label to assign
|
||||
"""
|
||||
print(f"Attempting to add label '{label}' to issue #{issue_number}")
|
||||
if label not in ALLOWED_LABELS:
|
||||
error_message = (
|
||||
f"Error: Label '{label}' is not an allowed label. Will not apply."
|
||||
)
|
||||
print(error_message)
|
||||
return {"status": "error", "message": error_message, "applied_label": None}
|
||||
|
||||
url = f"{BASE_URL}/repos/{OWNER}/{REPO}/issues/{issue_number}/labels"
|
||||
payload = [label]
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
payload = [label, BOT_LABEL]
|
||||
response = requests.post(url, headers=headers, json=payload, timeout=60)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
approval_instruction = (
|
||||
"Only label them when the user approves the labeling!"
|
||||
if is_interactive()
|
||||
else (
|
||||
"Do not ask for user approval for labeling! If you can't find a"
|
||||
" appropriate labels for the issue, do not label it."
|
||||
)
|
||||
)
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-2.5-pro-preview-05-06",
|
||||
name="adk_triaging_assistant",
|
||||
description="Triage ADK issues.",
|
||||
instruction="""
|
||||
You are a Github adk-python repo triaging bot. You will help get issues, and label them.
|
||||
instruction=f"""
|
||||
You are a Github adk-python repo triaging bot. You will help get issues, and recommend a label.
|
||||
IMPORTANT: {approval_instruction}
|
||||
Here are the rules for labeling:
|
||||
- If the user is asking about documentation-related questions, label it with "documentation".
|
||||
- If it's about session, memory services, label it with "services"
|
||||
- If it's about UI/web, label it with "question"
|
||||
- If it's about UI/web, label it with "web"
|
||||
- If the user is asking about a question, label it with "question"
|
||||
- If it's related to tools, label it with "tools"
|
||||
- If it's about agent evalaution, then label it with "eval".
|
||||
- If it's about streaming/live, label it with "live".
|
||||
- If it's about model support(non-Gemini, like Litellm, Ollama, OpenAI models), label it with "models".
|
||||
- If it's about tracing, label it with "tracing".
|
||||
- If it's agent orchestration, agent definition, label it with "core".
|
||||
- If you can't find a appropriate labels for the issue, return the issues to user to decide.
|
||||
- If you can't find a appropriate labels for the issue, follow the previous instruction that starts with "IMPORTANT:".
|
||||
|
||||
Present the followings in an easy to read format highlighting issue number and your label.
|
||||
- the issue summary in a few sentence
|
||||
- your label recommendation and justification
|
||||
""",
|
||||
tools=[
|
||||
list_issues,
|
||||
add_label_to_issue,
|
||||
],
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
safety_settings=[
|
||||
types.SafetySetting( # avoid false alarm about rolling dice.
|
||||
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
|
||||
threshold=types.HarmBlockThreshold.OFF,
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
# 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.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import time
|
||||
|
||||
import agent
|
||||
from dotenv import load_dotenv
|
||||
from google.adk.agents.run_config import RunConfig
|
||||
from google.adk.runners import InMemoryRunner
|
||||
from google.adk.sessions import Session
|
||||
from google.genai import types
|
||||
import requests
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
OWNER = os.getenv("OWNER", "google")
|
||||
REPO = os.getenv("REPO", "adk-python")
|
||||
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
|
||||
BASE_URL = "https://api.github.com"
|
||||
headers = {
|
||||
"Authorization": f"token {GITHUB_TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
}
|
||||
|
||||
if not GITHUB_TOKEN:
|
||||
print(
|
||||
"Warning: GITHUB_TOKEN environment variable not set. API calls might"
|
||||
" fail."
|
||||
)
|
||||
|
||||
|
||||
async def fetch_specific_issue_details(issue_number: int):
|
||||
"""Fetches details for a single issue if it's unlabelled."""
|
||||
if not GITHUB_TOKEN:
|
||||
print("Cannot fetch issue details: GITHUB_TOKEN is not set.")
|
||||
return None
|
||||
|
||||
url = f"{BASE_URL}/repos/{OWNER}/{REPO}/issues/{issue_number}"
|
||||
print(f"Fetching details for specific issue: {url}")
|
||||
try:
|
||||
response = requests.get(url, headers=headers, timeout=60)
|
||||
response.raise_for_status()
|
||||
issue_data = response.json()
|
||||
if not issue_data.get("labels") or len(issue_data["labels"]) == 0:
|
||||
print(f"Issue #{issue_number} is unlabelled. Proceeding.")
|
||||
return {
|
||||
"number": issue_data["number"],
|
||||
"title": issue_data["title"],
|
||||
"body": issue_data.get("body", ""),
|
||||
}
|
||||
else:
|
||||
print(f"Issue #{issue_number} is already labelled. Skipping.")
|
||||
return None
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error fetching issue #{issue_number}: {e}")
|
||||
if hasattr(e, "response") and e.response is not None:
|
||||
print(f"Response content: {e.response.text}")
|
||||
return None
|
||||
|
||||
|
||||
async def main():
|
||||
app_name = "triage_app"
|
||||
user_id_1 = "triage_user"
|
||||
runner = InMemoryRunner(
|
||||
agent=agent.root_agent,
|
||||
app_name=app_name,
|
||||
)
|
||||
session_11 = await runner.session_service.create_session(
|
||||
app_name=app_name, user_id=user_id_1
|
||||
)
|
||||
|
||||
async def run_agent_prompt(session: Session, prompt_text: str):
|
||||
content = types.Content(
|
||||
role="user", parts=[types.Part.from_text(text=prompt_text)]
|
||||
)
|
||||
print(f"\n>>>> Agent Prompt: {prompt_text}")
|
||||
final_agent_response_parts = []
|
||||
async for event in runner.run_async(
|
||||
user_id=user_id_1,
|
||||
session_id=session.id,
|
||||
new_message=content,
|
||||
run_config=RunConfig(save_input_blobs_as_artifacts=False),
|
||||
):
|
||||
if event.content.parts and event.content.parts[0].text:
|
||||
print(f"** {event.author} (ADK): {event.content.parts[0].text}")
|
||||
if event.author == agent.root_agent.name:
|
||||
final_agent_response_parts.append(event.content.parts[0].text)
|
||||
print(f"<<<< Agent Final Output: {''.join(final_agent_response_parts)}\n")
|
||||
|
||||
event_name = os.getenv("EVENT_NAME")
|
||||
issue_number_str = os.getenv("ISSUE_NUMBER")
|
||||
|
||||
if event_name == "issues" and issue_number_str:
|
||||
print(f"EVENT: Processing specific issue due to '{event_name}' event.")
|
||||
try:
|
||||
issue_number = int(issue_number_str)
|
||||
specific_issue = await fetch_specific_issue_details(issue_number)
|
||||
|
||||
if specific_issue:
|
||||
prompt = (
|
||||
f"A new GitHub issue #{specific_issue['number']} has been opened or"
|
||||
f" reopened. Title: \"{specific_issue['title']}\"\nBody:"
|
||||
f" \"{specific_issue['body']}\"\n\nBased on the rules, recommend an"
|
||||
" appropriate label and its justification."
|
||||
" Then, use the 'add_label_to_issue' tool to apply the label "
|
||||
"directly to this issue."
|
||||
f" The issue number is {specific_issue['number']}."
|
||||
)
|
||||
await run_agent_prompt(session_11, prompt)
|
||||
else:
|
||||
print(
|
||||
f"No unlabelled issue details found for #{issue_number} or an error"
|
||||
" occurred. Skipping agent interaction."
|
||||
)
|
||||
|
||||
except ValueError:
|
||||
print(f"Error: Invalid ISSUE_NUMBER received: {issue_number_str}")
|
||||
|
||||
else:
|
||||
print(f"EVENT: Processing batch of issues (event: {event_name}).")
|
||||
issue_count_str = os.getenv("ISSUE_COUNT_TO_PROCESS", "3")
|
||||
try:
|
||||
num_issues_to_process = int(issue_count_str)
|
||||
except ValueError:
|
||||
print(f"Warning: Invalid ISSUE_COUNT_TO_PROCESS. Defaulting to 3.")
|
||||
num_issues_to_process = 3
|
||||
|
||||
prompt = (
|
||||
f"List the first {num_issues_to_process} unlabelled open issues from"
|
||||
f" the {OWNER}/{REPO} repository. For each issue, provide a summary,"
|
||||
" recommend a label with justification, and then use the"
|
||||
" 'add_label_to_issue' tool to apply the recommended label directly."
|
||||
)
|
||||
await run_agent_prompt(session_11, prompt)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_time = time.time()
|
||||
print(
|
||||
"Script start time:",
|
||||
time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(start_time)),
|
||||
)
|
||||
print("------------------------------------")
|
||||
asyncio.run(main())
|
||||
end_time = time.time()
|
||||
print("------------------------------------")
|
||||
print(
|
||||
"Script end time:",
|
||||
time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(end_time)),
|
||||
)
|
||||
print("Total script execution time:", f"{end_time - start_time:.2f} seconds")
|
||||
@@ -15,6 +15,7 @@
|
||||
import random
|
||||
|
||||
from google.adk.agents import Agent
|
||||
from google.adk.examples.example import Example
|
||||
from google.adk.tools.example_tool import ExampleTool
|
||||
from google.genai import types
|
||||
|
||||
@@ -66,43 +67,47 @@ def check_prime(nums: list[int]) -> str:
|
||||
)
|
||||
|
||||
|
||||
example_tool = ExampleTool([
|
||||
{
|
||||
"input": {
|
||||
"role": "user",
|
||||
"parts": [{"text": "Roll a 6-sided die."}],
|
||||
},
|
||||
"output": [
|
||||
{"role": "model", "parts": [{"text": "I rolled a 4 for you."}]}
|
||||
],
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"role": "user",
|
||||
"parts": [{"text": "Is 7 a prime number?"}],
|
||||
},
|
||||
"output": [{
|
||||
"role": "model",
|
||||
"parts": [{"text": "Yes, 7 is a prime number."}],
|
||||
}],
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"role": "user",
|
||||
"parts": [{"text": "Roll a 10-sided die and check if it's prime."}],
|
||||
},
|
||||
"output": [
|
||||
{
|
||||
"role": "model",
|
||||
"parts": [{"text": "I rolled an 8 for you."}],
|
||||
},
|
||||
{
|
||||
"role": "model",
|
||||
"parts": [{"text": "8 is not a prime number."}],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
example_tool = ExampleTool(
|
||||
examples=[
|
||||
Example(
|
||||
input=types.UserContent(
|
||||
parts=[types.Part(text="Roll a 6-sided die.")]
|
||||
),
|
||||
output=[
|
||||
types.ModelContent(
|
||||
parts=[types.Part(text="I rolled a 4 for you.")]
|
||||
)
|
||||
],
|
||||
),
|
||||
Example(
|
||||
input=types.UserContent(
|
||||
parts=[types.Part(text="Is 7 a prime number?")]
|
||||
),
|
||||
output=[
|
||||
types.ModelContent(
|
||||
parts=[types.Part(text="Yes, 7 is a prime number.")]
|
||||
)
|
||||
],
|
||||
),
|
||||
Example(
|
||||
input=types.UserContent(
|
||||
parts=[
|
||||
types.Part(
|
||||
text="Roll a 10-sided die and check if it's prime."
|
||||
)
|
||||
]
|
||||
),
|
||||
output=[
|
||||
types.ModelContent(
|
||||
parts=[types.Part(text="I rolled an 8 for you.")]
|
||||
),
|
||||
types.ModelContent(
|
||||
parts=[types.Part(text="8 is not a prime number.")]
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
prime_agent = Agent(
|
||||
name="prime_agent",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# 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.
|
||||
|
||||
from . import agent
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
# 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.
|
||||
|
||||
import random
|
||||
|
||||
from google.adk import Agent
|
||||
from google.adk.tools.tool_context import ToolContext
|
||||
from google.genai import types
|
||||
|
||||
|
||||
def roll_die(sides: int, tool_context: ToolContext) -> int:
|
||||
"""Roll a die and return the rolled result.
|
||||
|
||||
Args:
|
||||
sides: The integer number of sides the die has.
|
||||
|
||||
Returns:
|
||||
An integer of the result of rolling the die.
|
||||
"""
|
||||
result = random.randint(1, sides)
|
||||
if not 'rolls' in tool_context.state:
|
||||
tool_context.state['rolls'] = []
|
||||
|
||||
tool_context.state['rolls'] = tool_context.state['rolls'] + [result]
|
||||
return result
|
||||
|
||||
|
||||
async def check_prime(nums: list[int]) -> str:
|
||||
"""Check if a given list of numbers are prime.
|
||||
|
||||
Args:
|
||||
nums: The list of numbers to check.
|
||||
|
||||
Returns:
|
||||
A str indicating which number is prime.
|
||||
"""
|
||||
primes = set()
|
||||
for number in nums:
|
||||
number = int(number)
|
||||
if number <= 1:
|
||||
continue
|
||||
is_prime = True
|
||||
for i in range(2, int(number**0.5) + 1):
|
||||
if number % i == 0:
|
||||
is_prime = False
|
||||
break
|
||||
if is_prime:
|
||||
primes.add(number)
|
||||
return (
|
||||
'No prime numbers found.'
|
||||
if not primes
|
||||
else f"{', '.join(str(num) for num in primes)} are prime numbers."
|
||||
)
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
model='gemini-2.0-flash-live-preview-04-09', # for Vertex project
|
||||
# model='gemini-2.0-flash-live-001', # for AI studio key
|
||||
name='hello_world_agent',
|
||||
description=(
|
||||
'hello world agent that can roll a dice of 8 sides and check prime'
|
||||
' numbers.'
|
||||
),
|
||||
instruction="""
|
||||
You roll dice and answer questions about the outcome of the dice rolls.
|
||||
You can roll dice of different sizes.
|
||||
You can use multiple tools in parallel by calling functions in parallel(in one request and in one round).
|
||||
It is ok to discuss previous dice roles, and comment on the dice rolls.
|
||||
When you are asked to roll a die, you must call the roll_die tool with the number of sides. Be sure to pass in an integer. Do not pass in a string.
|
||||
You should never roll a die on your own.
|
||||
When checking prime numbers, call the check_prime tool with a list of integers. Be sure to pass in a list of integers. You should never pass in a string.
|
||||
You should not check prime numbers before calling the tool.
|
||||
When you are asked to roll a die and check prime numbers, you should always make the following two function calls:
|
||||
1. You should first call the roll_die tool to get a roll. Wait for the function response before calling the check_prime tool.
|
||||
2. After you get the function response from roll_die tool, you should call the check_prime tool with the roll_die result.
|
||||
2.1 If user asks you to check primes based on previous rolls, make sure you include the previous rolls in the list.
|
||||
3. When you respond, you must include the roll_die result from step 1.
|
||||
You should always perform the previous 3 steps when asking for a roll and checking prime numbers.
|
||||
You should not rely on the previous history on prime results.
|
||||
""",
|
||||
tools=[
|
||||
roll_die,
|
||||
check_prime,
|
||||
],
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
safety_settings=[
|
||||
types.SafetySetting( # avoid false alarm about rolling dice.
|
||||
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
|
||||
threshold=types.HarmBlockThreshold.OFF,
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
# Simplistic Live (Bidi-Streaming) Agent
|
||||
This project provides a basic example of a live, bidirectional streaming agent
|
||||
designed for testing and experimentation.
|
||||
|
||||
You can see full documentation [here](https://google.github.io/adk-docs/streaming/).
|
||||
|
||||
## Getting Started
|
||||
|
||||
Follow these steps to get the agent up and running:
|
||||
|
||||
1. **Start the ADK Web Server**
|
||||
Open your terminal, navigate to the root directory that contains the
|
||||
`live_bidi_streaming_agent` folder, and execute the following command:
|
||||
```bash
|
||||
adk web
|
||||
```
|
||||
|
||||
2. **Access the ADK Web UI**
|
||||
Once the server is running, open your web browser and navigate to the URL
|
||||
provided in the terminal (it will typically be `http://localhost:8000`).
|
||||
|
||||
3. **Select the Agent**
|
||||
In the top-left corner of the ADK Web UI, use the dropdown menu to select
|
||||
this agent.
|
||||
|
||||
4. **Start Streaming**
|
||||
Click on either the **Audio** or **Video** icon located near the chat input
|
||||
box to begin the streaming session.
|
||||
|
||||
5. **Interact with the Agent**
|
||||
You can now begin talking to the agent, and it will respond in real-time.
|
||||
|
||||
## Usage Notes
|
||||
|
||||
* You only need to click the **Audio** or **Video** button once to initiate the
|
||||
stream. The current version does not support stopping and restarting the stream
|
||||
by clicking the button again during a session.
|
||||
@@ -1,8 +1,7 @@
|
||||
This agent connects to a local MCP server via sse.
|
||||
This agent connects to a local MCP server via Streamable HTTP.
|
||||
|
||||
To run this agent, start the local MCP server first by :
|
||||
|
||||
```bash
|
||||
uv run filesystem_server.py
|
||||
```
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import os
|
||||
from google.adk.agents.llm_agent import LlmAgent
|
||||
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPServerParams
|
||||
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
|
||||
from google.adk.tools.mcp_tool.mcp_toolset import SseServerParams
|
||||
|
||||
_allowed_path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ def get_weather(city: str) -> dict:
|
||||
"status": "success",
|
||||
"report": (
|
||||
"The weather in New York is sunny with a temperature of 25 degrees"
|
||||
" Celsius (41 degrees Fahrenheit)."
|
||||
" Celsius (77 degrees Fahrenheit)."
|
||||
),
|
||||
}
|
||||
else:
|
||||
|
||||
+36
-19
@@ -25,27 +25,32 @@ classifiers = [ # List of https://pypi.org/classifiers/
|
||||
]
|
||||
dependencies = [
|
||||
# go/keep-sorted start
|
||||
"authlib>=1.5.1", # For RestAPI Tool
|
||||
"click>=8.1.8", # For CLI tools
|
||||
"fastapi>=0.115.0", # FastAPI framework
|
||||
"google-api-python-client>=2.157.0", # Google API client discovery
|
||||
"google-cloud-aiplatform[agent_engines]>=1.95.1", # For VertexAI integrations, e.g. example store.
|
||||
"google-cloud-secret-manager>=2.22.0", # Fetching secrets in RestAPI Tool
|
||||
"google-cloud-speech>=2.30.0", # For Audio Transcription
|
||||
"google-cloud-storage>=2.18.0, <3.0.0", # For GCS Artifact service
|
||||
"google-genai>=1.17.0", # Google GenAI SDK
|
||||
"graphviz>=0.20.2", # Graphviz for graph rendering
|
||||
"mcp>=1.8.0;python_version>='3.10'", # For MCP Toolset
|
||||
"opentelemetry-api>=1.31.0", # OpenTelemetry
|
||||
"anyio>=4.9.0;python_version>='3.10'", # For MCP Session Manager
|
||||
"authlib>=1.5.1", # For RestAPI Tool
|
||||
"click>=8.1.8", # For CLI tools
|
||||
"fastapi>=0.115.0", # FastAPI framework
|
||||
"google-api-python-client>=2.157.0", # Google API client discovery
|
||||
"google-cloud-aiplatform[agent_engines]>=1.95.1", # For VertexAI integrations, e.g. example store.
|
||||
"google-cloud-secret-manager>=2.22.0", # Fetching secrets in RestAPI Tool
|
||||
"google-cloud-speech>=2.30.0", # For Audio Transcription
|
||||
"google-cloud-storage>=2.18.0, <3.0.0", # For GCS Artifact service
|
||||
"google-genai>=1.17.0", # Google GenAI SDK
|
||||
"graphviz>=0.20.2", # Graphviz for graph rendering
|
||||
"mcp>=1.8.0;python_version>='3.10'", # For MCP Toolset
|
||||
"opentelemetry-api>=1.31.0", # OpenTelemetry
|
||||
"opentelemetry-exporter-gcp-trace>=1.9.0",
|
||||
"opentelemetry-sdk>=1.31.0",
|
||||
"pydantic>=2.0, <3.0.0", # For data validation/models
|
||||
"python-dotenv>=1.0.0", # To manage environment variables
|
||||
"PyYAML>=6.0.2", # For APIHubToolset.
|
||||
"sqlalchemy>=2.0", # SQL database ORM
|
||||
"tzlocal>=5.3", # Time zone utilities
|
||||
"pydantic>=2.0, <3.0.0", # For data validation/models
|
||||
"python-dateutil>=2.9.0.post0", # For Vertext AI Session Service
|
||||
"python-dotenv>=1.0.0", # To manage environment variables
|
||||
"PyYAML>=6.0.2", # For APIHubToolset.
|
||||
"requests>=2.32.4",
|
||||
"sqlalchemy>=2.0", # SQL database ORM
|
||||
"starlette>=0.46.2", # For FastAPI CLI
|
||||
"typing-extensions>=4.5, <5",
|
||||
"uvicorn>=0.34.0", # ASGI server for FastAPI
|
||||
"tzlocal>=5.3", # Time zone utilities
|
||||
"uvicorn>=0.34.0", # ASGI server for FastAPI
|
||||
"websockets>=15.0.1", # For BaseLlmFlow
|
||||
# go/keep-sorted end
|
||||
]
|
||||
dynamic = ["version"]
|
||||
@@ -71,6 +76,12 @@ dev = [
|
||||
# go/keep-sorted end
|
||||
]
|
||||
|
||||
a2a = [
|
||||
# go/keep-sorted start
|
||||
"a2a-sdk>=0.2.7;python_version>='3.10'"
|
||||
# go/keep-sorted end
|
||||
]
|
||||
|
||||
eval = [
|
||||
# go/keep-sorted start
|
||||
"google-cloud-aiplatform[evaluation]>=1.87.0",
|
||||
@@ -84,7 +95,7 @@ test = [
|
||||
"anthropic>=0.43.0", # For anthropic model tests
|
||||
"langchain-community>=0.3.17",
|
||||
"langgraph>=0.2.60", # For LangGraphAgent
|
||||
"litellm>=1.71.2", # For LiteLLM tests
|
||||
"litellm>=1.71.2", # For LiteLLM tests
|
||||
"llama-index-readers-file>=0.4.0", # For retrieval tests
|
||||
|
||||
"pytest-asyncio>=0.25.0",
|
||||
@@ -140,24 +151,30 @@ pyink-annotation-pragmas = [
|
||||
requires = ["flit_core >=3.8,<4"]
|
||||
build-backend = "flit_core.buildapi"
|
||||
|
||||
|
||||
[tool.flit.sdist]
|
||||
include = ['src/**/*', 'README.md', 'pyproject.toml', 'LICENSE']
|
||||
exclude = ['src/**/*.sh']
|
||||
|
||||
|
||||
[tool.flit.module]
|
||||
name = "google.adk"
|
||||
include = ["py.typed"]
|
||||
|
||||
|
||||
[tool.isort]
|
||||
profile = "google"
|
||||
single_line_exclusions = []
|
||||
line_length = 200 # Prevent line wrap flickering.
|
||||
known_third_party = ["google.adk"]
|
||||
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
asyncio_mode = "auto"
|
||||
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.9"
|
||||
exclude = "tests/"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,13 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,177 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
module containing utilities for conversion betwen A2A Part and Google GenAI Part
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
try:
|
||||
from a2a import types as a2a_types
|
||||
except ImportError as e:
|
||||
if sys.version_info < (3, 10):
|
||||
raise ImportError(
|
||||
'A2A Tool requires Python 3.10 or above. Please upgrade your Python'
|
||||
' version.'
|
||||
) from e
|
||||
else:
|
||||
raise e
|
||||
|
||||
from google.genai import types as genai_types
|
||||
|
||||
from ...utils.feature_decorator import working_in_progress
|
||||
|
||||
logger = logging.getLogger('google_adk.' + __name__)
|
||||
|
||||
A2A_DATA_PART_METADATA_TYPE_KEY = 'type'
|
||||
A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL = 'function_call'
|
||||
A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE = 'function_response'
|
||||
|
||||
|
||||
@working_in_progress
|
||||
def convert_a2a_part_to_genai_part(
|
||||
a2a_part: a2a_types.Part,
|
||||
) -> Optional[genai_types.Part]:
|
||||
"""Convert an A2A Part to a Google GenAI Part."""
|
||||
part = a2a_part.root
|
||||
if isinstance(part, a2a_types.TextPart):
|
||||
return genai_types.Part(text=part.text)
|
||||
|
||||
if isinstance(part, a2a_types.FilePart):
|
||||
if isinstance(part.file, a2a_types.FileWithUri):
|
||||
return genai_types.Part(
|
||||
file_data=genai_types.FileData(
|
||||
file_uri=part.file.uri, mime_type=part.file.mimeType
|
||||
)
|
||||
)
|
||||
|
||||
elif isinstance(part.file, a2a_types.FileWithBytes):
|
||||
return genai_types.Part(
|
||||
inline_data=genai_types.Blob(
|
||||
data=part.file.bytes.encode('utf-8'), mime_type=part.file.mimeType
|
||||
)
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
'Cannot convert unsupported file type: %s for A2A part: %s',
|
||||
type(part.file),
|
||||
a2a_part,
|
||||
)
|
||||
return None
|
||||
|
||||
if isinstance(part, a2a_types.DataPart):
|
||||
# Conver the Data Part to funcall and function reponse.
|
||||
# This is mainly for converting human in the loop and auth request and
|
||||
# response.
|
||||
# TODO once A2A defined how to suervice such information, migrate below
|
||||
# logic accordinlgy
|
||||
if part.metadata and A2A_DATA_PART_METADATA_TYPE_KEY in part.metadata:
|
||||
if (
|
||||
part.metadata[A2A_DATA_PART_METADATA_TYPE_KEY]
|
||||
== A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
|
||||
):
|
||||
return genai_types.Part(
|
||||
function_call=genai_types.FunctionCall.model_validate(
|
||||
part.data, by_alias=True
|
||||
)
|
||||
)
|
||||
if (
|
||||
part.metadata[A2A_DATA_PART_METADATA_TYPE_KEY]
|
||||
== A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE
|
||||
):
|
||||
return genai_types.Part(
|
||||
function_response=genai_types.FunctionResponse.model_validate(
|
||||
part.data, by_alias=True
|
||||
)
|
||||
)
|
||||
return genai_types.Part(text=json.dumps(part.data))
|
||||
|
||||
logger.warning(
|
||||
'Cannot convert unsupported part type: %s for A2A part: %s',
|
||||
type(part),
|
||||
a2a_part,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@working_in_progress
|
||||
def convert_genai_part_to_a2a_part(
|
||||
part: genai_types.Part,
|
||||
) -> Optional[a2a_types.Part]:
|
||||
"""Convert a Google GenAI Part to an A2A Part."""
|
||||
if part.text:
|
||||
return a2a_types.TextPart(text=part.text)
|
||||
|
||||
if part.file_data:
|
||||
return a2a_types.FilePart(
|
||||
file=a2a_types.FileWithUri(
|
||||
uri=part.file_data.file_uri,
|
||||
mimeType=part.file_data.mime_type,
|
||||
)
|
||||
)
|
||||
|
||||
if part.inline_data:
|
||||
return a2a_types.Part(
|
||||
root=a2a_types.FilePart(
|
||||
file=a2a_types.FileWithBytes(
|
||||
bytes=part.inline_data.data,
|
||||
mimeType=part.inline_data.mime_type,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Conver the funcall and function reponse to A2A DataPart.
|
||||
# This is mainly for converting human in the loop and auth request and
|
||||
# response.
|
||||
# TODO once A2A defined how to suervice such information, migrate below
|
||||
# logic accordinlgy
|
||||
if part.function_call:
|
||||
return a2a_types.Part(
|
||||
root=a2a_types.DataPart(
|
||||
data=part.function_call.model_dump(
|
||||
by_alias=True, exclude_none=True
|
||||
),
|
||||
metadata={
|
||||
A2A_DATA_PART_METADATA_TYPE_KEY: (
|
||||
A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
|
||||
)
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
if part.function_response:
|
||||
return a2a_types.Part(
|
||||
root=a2a_types.DataPart(
|
||||
data=part.function_response.model_dump(
|
||||
by_alias=True, exclude_none=True
|
||||
),
|
||||
metadata={
|
||||
A2A_DATA_PART_METADATA_TYPE_KEY: (
|
||||
A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE
|
||||
)
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
logger.warning(
|
||||
'Cannot convert unsupported part for Google GenAI part: %s',
|
||||
part,
|
||||
)
|
||||
return None
|
||||
@@ -22,6 +22,7 @@ from pydantic import BaseModel
|
||||
from pydantic import ConfigDict
|
||||
|
||||
from ..artifacts.base_artifact_service import BaseArtifactService
|
||||
from ..auth.credential_service.base_credential_service import BaseCredentialService
|
||||
from ..memory.base_memory_service import BaseMemoryService
|
||||
from ..sessions.base_session_service import BaseSessionService
|
||||
from ..sessions.session import Session
|
||||
@@ -115,6 +116,7 @@ class InvocationContext(BaseModel):
|
||||
artifact_service: Optional[BaseArtifactService] = None
|
||||
session_service: BaseSessionService
|
||||
memory_service: Optional[BaseMemoryService] = None
|
||||
credential_service: Optional[BaseCredentialService] = None
|
||||
|
||||
invocation_id: str
|
||||
"""The id of this invocation context. Readonly."""
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
import logging
|
||||
import sys
|
||||
@@ -68,6 +70,15 @@ class RunConfig(BaseModel):
|
||||
input_audio_transcription: Optional[types.AudioTranscriptionConfig] = None
|
||||
"""Input transcription for live agents with audio input from user."""
|
||||
|
||||
realtime_input_config: Optional[types.RealtimeInputConfig] = None
|
||||
"""Realtime input config for live agents with audio input from user."""
|
||||
|
||||
enable_affective_dialog: Optional[bool] = None
|
||||
"""If enabled, the model will detect emotions and adapt its responses accordingly."""
|
||||
|
||||
proactivity: Optional[types.ProactivityConfig] = None
|
||||
"""Configures the proactivity of the model. This allows the model to respond proactively to the input and to ignore irrelevant input."""
|
||||
|
||||
max_llm_calls: int = 500
|
||||
"""
|
||||
A limit on the total number of llm calls for a given run.
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
@@ -75,6 +77,8 @@ class OAuth2Auth(BaseModelWithConfig):
|
||||
auth_code: Optional[str] = None
|
||||
access_token: Optional[str] = None
|
||||
refresh_token: Optional[str] = None
|
||||
expires_at: Optional[int] = None
|
||||
expires_in: Optional[int] = None
|
||||
|
||||
|
||||
class ServiceAccountCredential(BaseModelWithConfig):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user