chore: import upstream snapshot with attribution
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

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,76 @@
# ADK Pull Request Triaging Assistant
The ADK Pull Request (PR) Triaging Assistant is a Python-based agent designed to help manage and triage GitHub pull requests for the `google/adk-python` repository. It uses a large language model to analyze new and unlabelled pull requests, recommend appropriate labels, assign a reviewer, and check contribution guides based on a predefined set of rules.
This agent can be operated in two distinct modes:
- an interactive mode for local use
- 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 pull requests.
### 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 or posting a comment to a GitHub pull request.
### 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 PR 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:
- **Pull Request Events**: The workflow executes automatically whenever a new PR is `opened` or an existing one is `reopened` or `edited`.
### Automated Labeling
When running as part of the GitHub workflow, the agent operates non-interactively. It identifies and applies the best label or posts a comment 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/pr-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
```
### 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 `pull_requests: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.
@@ -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,425 @@
# 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 pathlib import Path
from typing import Any
from adk_pr_triaging_agent.settings import GITHUB_BASE_URL
from adk_pr_triaging_agent.settings import IS_INTERACTIVE
from adk_pr_triaging_agent.settings import OWNER
from adk_pr_triaging_agent.settings import REPO
from adk_pr_triaging_agent.utils import error_response
from adk_pr_triaging_agent.utils import get_diff
from adk_pr_triaging_agent.utils import get_request
from adk_pr_triaging_agent.utils import is_assignable
from adk_pr_triaging_agent.utils import post_request
from adk_pr_triaging_agent.utils import read_file
from adk_pr_triaging_agent.utils import run_graphql_query
from google.adk import Agent
import requests
ALLOWED_LABELS = [
"documentation",
"services",
"tools",
"mcp",
"eval",
"live",
"models",
"tracing",
"core",
"web",
]
# Component label -> GitHub login of the owner who shepherds that component.
# The owner becomes the PR's assignee so the contributor can see who is
# handling their PR. github login != corp ldap, so this is the login form. Keep
# in sync with the OWNERS file (the authority) and adk_triaging_agent's map.
LABEL_TO_OWNER = {
"documentation": "joefernandez",
"services": "DeanChensj",
"tools": "xuanyang15",
"mcp": "wukath",
"eval": "ankursharmas",
"live": "wuliang229",
"models": "xuanyang15",
"tracing": "jawoszek",
"core": "DeanChensj",
"web": "wyf7107",
}
CONTRIBUTING_MD = read_file(
Path(__file__).resolve().parents[4] / "CONTRIBUTING.md"
)
APPROVAL_INSTRUCTION = (
"Do not ask for user approval for labeling, commenting, or assigning!"
" If you can't find appropriate labels for the PR, do not label it."
)
if IS_INTERACTIVE:
APPROVAL_INSTRUCTION = (
"Only label, comment, or assign when the user approves the action!"
)
def get_pull_request_details(pr_number: int) -> str:
"""Get the details of the specified pull request.
Args:
pr_number: number of the GitHub pull request.
Returns:
The status of this request, with the details when successful.
"""
print(f"Fetching details for PR #{pr_number} from {OWNER}/{REPO}")
query = """
query($owner: String!, $repo: String!, $prNumber: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $prNumber) {
id
number
title
body
state
author {
login
}
labels(last: 10) {
nodes {
name
}
}
assignees(first: 10) {
nodes {
login
}
}
files(last: 50) {
nodes {
path
}
}
comments(last: 50) {
nodes {
id
body
createdAt
author {
login
}
}
}
commits(last: 50) {
nodes {
commit {
url
message
}
}
}
statusCheckRollup {
state
contexts(last: 20) {
nodes {
... on StatusContext {
context
state
targetUrl
}
... on CheckRun {
name
status
conclusion
detailsUrl
}
}
}
}
}
}
}
"""
variables = {"owner": OWNER, "repo": REPO, "prNumber": pr_number}
url = f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/pulls/{pr_number}"
try:
response = run_graphql_query(query, variables)
if "errors" in response:
return error_response(str(response["errors"]))
pr = response.get("data", {}).get("repository", {}).get("pullRequest")
if not pr:
return error_response(f"Pull Request #{pr_number} not found.")
# Filter out main merge commits.
original_commits = pr.get("commits", {}).get("nodes", {})
if original_commits:
filtered_commits = [
commit_node
for commit_node in original_commits
if not commit_node["commit"]["message"].startswith(
"Merge branch 'main' into"
)
]
pr["commits"]["nodes"] = filtered_commits
# Get diff of the PR and truncate it to avoid exceeding the maximum tokens.
pr["diff"] = get_diff(url)[:10000]
return {"status": "success", "pull_request": pr}
except requests.exceptions.RequestException as e:
return error_response(str(e))
def add_label_to_pr(pr_number: int, label: str) -> dict[str, Any]:
"""Adds a specified label on a pull request.
Args:
pr_number: the number of the GitHub pull request
label: the label to add
Returns:
The status of this request, with the applied label and response when
successful.
"""
print(f"Attempting to add label '{label}' to PR #{pr_number}")
if label not in ALLOWED_LABELS:
return error_response(
f"Error: Label '{label}' is not an allowed label. Will not apply."
)
# Pull Request is a special issue in GitHub, so we can use issue url for PR.
label_url = (
f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/issues/{pr_number}/labels"
)
label_payload = [label]
try:
response = post_request(label_url, label_payload)
except requests.exceptions.RequestException as e:
return error_response(f"Error: {e}")
return {
"status": "success",
"applied_label": label,
"response": response,
}
def assign_owner_to_pr(pr_number: int, label: str) -> dict[str, Any]:
"""Assign the component owner (the shepherd) to a PR based on its label.
The owner is looked up from `LABEL_TO_OWNER` so the contributor can see who is
shepherding their PR. GitHub only allows assigning users with
repo write/triage access, so a non-assignable owner is reported as skipped
rather than silently dropped.
Args:
pr_number: the number of the GitHub pull request
label: the component label the PR was triaged into
Returns:
The status of this request, with the assigned owner when successful.
"""
owner = LABEL_TO_OWNER.get(label)
if not owner:
return error_response(f"Error: no owner mapped for label '{label}'.")
print(f"Attempting to assign owner '{owner}' to PR #{pr_number}")
if not is_assignable(owner):
return {
"status": "skipped",
"reason": f"'{owner}' is not assignable (needs repo access)",
"owner": owner,
}
# Pull Request is a special issue in GitHub, so we can use the issue url.
assignee_url = (
f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/issues/{pr_number}/assignees"
)
try:
response = post_request(assignee_url, {"assignees": [owner]})
except requests.exceptions.RequestException as e:
return error_response(f"Error: {e}")
return {
"status": "success",
"assigned_owner": owner,
"response": response,
}
def add_comment_to_pr(pr_number: int, comment: str) -> dict[str, Any]:
"""Add the specified comment to the given PR number.
Args:
pr_number: the number of the GitHub pull request
comment: the comment to add
Returns:
The status of this request, with the applied comment when successful.
"""
print(f"Attempting to add comment '{comment}' to issue #{pr_number}")
# Pull Request is a special issue in GitHub, so we can use issue url for PR.
url = f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/issues/{pr_number}/comments"
payload = {"body": comment}
try:
post_request(url, payload)
except requests.exceptions.RequestException as e:
return error_response(f"Error: {e}")
return {
"status": "success",
"added_comment": comment,
}
def list_untriaged_pull_requests(pr_count: int) -> dict[str, Any]:
"""List open pull requests that need triaging.
Returns pull requests that need triaging (i.e. do not have google-contributor
label and do not have any allowed triage category labels).
Args:
pr_count: number of pull requests to return
Returns:
The status of this request, with a list of pull requests when successful.
"""
url = f"{GITHUB_BASE_URL}/search/issues"
query = f"repo:{OWNER}/{REPO} is:open is:pr"
params = {
"q": query,
"sort": "updated",
"order": "desc",
"per_page": 100,
"page": 1,
}
try:
response = get_request(url, params)
except requests.exceptions.RequestException as e:
return error_response(f"Error: {e}")
issues = response.get("items", [])
triage_labels = set(ALLOWED_LABELS)
untriaged_prs = []
for pr in issues:
pr_labels = {label["name"] for label in pr.get("labels", [])}
if "google-contributor" in pr_labels:
continue
# If it already has any of the ALLOWED_LABELS, skip it.
if pr_labels & triage_labels:
continue
untriaged_prs.append({
"number": pr["number"],
"title": pr["title"],
})
if len(untriaged_prs) >= pr_count:
break
return {"status": "success", "pull_requests": untriaged_prs}
root_agent = Agent(
model="gemini-3.5-flash",
name="adk_pr_triaging_assistant",
description="Triage ADK pull requests.",
instruction=f"""
# 1. Identity
You are a Pull Request (PR) triaging bot for the GitHub {REPO} repo with the owner {OWNER}.
# 2. Responsibilities
Your core responsibility includes:
- Get the pull request details.
- Add a label to the pull request.
- Assign the component owner (the shepherd) to the pull request.
- Check if the pull request is following the contribution guidelines.
- Add a comment to the pull request if it's not following the guidelines.
**IMPORTANT: {APPROVAL_INSTRUCTION}**
# 3. Guidelines & Rules
Here are the rules for labeling:
- If the PR is about documentations, label it with "documentation".
- If it's about session, memory, artifacts services, label it with "services"
- If it's about UI/web, label it with "web"
- If it's related to tools, label it with "tools"
- If it's about agent evaluation, 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 it's about Model Context Protocol (e.g. MCP tool, MCP toolset, MCP session management etc.), label it with "mcp".
- If you can't find an appropriate labels for the PR, follow the previous instruction that starts with "IMPORTANT:".
Here is the contribution guidelines:
`{CONTRIBUTING_MD}`
Here are the guidelines for checking if the PR is following the guidelines:
- The "statusCheckRollup" in the pull request details may help you to identify if the PR is following some of the guidelines (e.g. CLA compliance).
Here are the guidelines for the comment:
- **Be Polite and Helpful:** Start with a friendly tone.
- **Be Specific:** Clearly list only the sections from the contribution guidelines that are still missing.
- **Address the Author:** Mention the PR author by their username (e.g., `@username`).
- **Provide Context:** Explain *why* the information or action is needed.
- **Do not be repetitive:** If you have already commented on an PR asking for information, do not comment again unless new information has been added and it's still incomplete.
- **Identify yourself:** Include a bolded note (e.g. "Response from ADK Triaging Agent") in your comment to indicate this comment was added by an ADK Answering Agent.
**Example Comment for a PR:**
> **Response from ADK Triaging Agent**
>
> Hello @[pr-author-username], thank you for creating this PR!
>
> This PR is a bug fix, could you please associate the github issue with this PR? If there is no existing issue, could you please create one?
>
> In addition, could you please provide logs or screenshot after the fix is applied?
>
> This information will help reviewers to review your PR more efficiently. Thanks!
# 4. Steps
- If you are asked to find pull requests that need triaging, use `list_untriaged_pull_requests` first.
- For each pull request to be triaged:
- Call the `get_pull_request_details` tool to get the details of the PR.
- Skip the PR (i.e. do not label or comment) if any of the following is true:
- the PR is closed
- the PR is labeled with "google-contributor"
- the PR is already labelled with the above labels (e.g. "documentation", "services", "tools", etc.).
- Check if the PR is following the contribution guidelines.
- If it's not following the guidelines, recommend or add a comment to the PR that points to the contribution guidelines (https://github.com/google/adk-python/blob/main/CONTRIBUTING.md).
- If it's following the guidelines, recommend or add a label to the PR.
- After you add a component label, assign the component owner (the shepherd) to the PR:
- Call `assign_owner_to_pr` with the same label you applied.
- Skip assignment if the PR already has an assignee.
- If the tool reports the owner is not assignable, just note it; do not comment about it.
# 5. Output
Present the following in an easy to read format highlighting PR number and your label.
- The PR summary in a few sentence
- The label you recommended or added with the justification
- The owner you assigned (or why you did not)
- The comment you recommended or added to the PR with the justification
""",
tools=[
list_untriaged_pull_requests,
get_pull_request_details,
add_label_to_pr,
assign_owner_to_pr,
add_comment_to_pr,
],
)
@@ -0,0 +1,77 @@
# 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
import time
from adk_pr_triaging_agent import agent
from adk_pr_triaging_agent.settings import OWNER
from adk_pr_triaging_agent.settings import PR_COUNT_TO_PROCESS
from adk_pr_triaging_agent.settings import PULL_REQUEST_NUMBER
from adk_pr_triaging_agent.settings import REPO
from adk_pr_triaging_agent.utils import call_agent_async
from adk_pr_triaging_agent.utils import parse_number_string
from google.adk.cli.utils import logs
from google.adk.runners import InMemoryRunner
APP_NAME = "adk_pr_triaging_app"
USER_ID = "adk_pr_triaging_user"
logs.setup_adk_logger(level=logging.DEBUG)
async def main():
runner = InMemoryRunner(
agent=agent.root_agent,
app_name=APP_NAME,
)
session = await runner.session_service.create_session(
app_name=APP_NAME, user_id=USER_ID
)
pr_number = parse_number_string(PULL_REQUEST_NUMBER)
if pr_number:
prompt = f"Please triage pull request #{pr_number}!"
else:
pr_count = parse_number_string(PR_COUNT_TO_PROCESS, default_value=10)
print(
"No pull request number received. Operating in batch mode (limit:"
f" {pr_count})."
)
prompt = (
f"Please use 'list_untriaged_pull_requests' to find {pr_count} pull"
" requests that need triaging, then triage each one according to your"
" instructions."
)
response = await call_agent_async(runner, USER_ID, session.id, prompt)
print(f"<<<< Agent Final Output: {response}\n")
if __name__ == "__main__":
start_time = time.time()
print(
f"Start triaging {OWNER}/{REPO} pull request #{PULL_REQUEST_NUMBER} at"
f" {time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(start_time))}"
)
print("-" * 80)
asyncio.run(main())
print("-" * 80)
end_time = time.time()
print(
"Triaging finished at"
f" {time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(end_time))}",
)
print("Total script execution time:", f"{end_time - start_time:.2f} seconds")
@@ -0,0 +1,33 @@
# 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 os
from dotenv import load_dotenv
load_dotenv(override=True)
GITHUB_BASE_URL = "https://api.github.com"
GITHUB_GRAPHQL_URL = GITHUB_BASE_URL + "/graphql"
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
if not GITHUB_TOKEN:
raise ValueError("GITHUB_TOKEN environment variable not set")
OWNER = os.getenv("OWNER", "google")
REPO = os.getenv("REPO", "adk-python")
PULL_REQUEST_NUMBER = os.getenv("PULL_REQUEST_NUMBER")
PR_COUNT_TO_PROCESS = os.getenv("PR_COUNT_TO_PROCESS", "10")
IS_INTERACTIVE = os.environ.get("INTERACTIVE", "1").lower() in ["true", "1"]
@@ -0,0 +1,133 @@
# 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 sys
from typing import Any
from adk_pr_triaging_agent.settings import GITHUB_BASE_URL
from adk_pr_triaging_agent.settings import GITHUB_GRAPHQL_URL
from adk_pr_triaging_agent.settings import GITHUB_TOKEN
from adk_pr_triaging_agent.settings import OWNER
from adk_pr_triaging_agent.settings import REPO
from google.adk.agents.run_config import RunConfig
from google.adk.runners import Runner
from google.genai import types
import requests
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json",
}
diff_headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3.diff",
}
def run_graphql_query(query: str, variables: dict[str, Any]) -> dict[str, Any]:
"""Executes a GraphQL query."""
payload = {"query": query, "variables": variables}
response = requests.post(
GITHUB_GRAPHQL_URL, headers=headers, json=payload, timeout=60
)
response.raise_for_status()
return response.json()
def get_request(url: str, params: dict[str, Any] | None = None) -> Any:
"""Executes a GET request."""
if params is None:
params = {}
response = requests.get(url, headers=headers, params=params, timeout=60)
response.raise_for_status()
return response.json()
def get_diff(url: str) -> str:
"""Executes a GET request for a diff."""
response = requests.get(url, headers=diff_headers)
response.raise_for_status()
return response.text
def post_request(url: str, payload: Any) -> dict[str, Any]:
"""Executes a POST request."""
response = requests.post(url, headers=headers, json=payload, timeout=60)
response.raise_for_status()
return response.json()
def is_assignable(login: str) -> bool:
"""Whether a GitHub user can be assigned to an issue/PR in this repo."""
# GitHub only allows assignees with repo write/triage access and silently
# drops others from an assignee POST; check first so callers can report the
# skip instead of a silent no-op.
url = f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/assignees/{login}"
response = requests.get(url, headers=headers, timeout=60)
return response.status_code == 204
def error_response(error_message: str) -> dict[str, Any]:
"""Returns an error response."""
return {"status": "error", "error_message": error_message}
def read_file(file_path: str) -> str:
"""Read the content of the given file."""
try:
with open(file_path, "r") as f:
return f.read()
except FileNotFoundError:
print(f"Error: File not found: {file_path}.")
return ""
def parse_number_string(number_str: str | None, default_value: int = 0) -> int:
"""Parse a number from the given string."""
if not number_str:
return default_value
try:
return int(number_str)
except ValueError:
print(
f"Warning: Invalid number string: {number_str}. Defaulting to"
f" {default_value}.",
file=sys.stderr,
)
return default_value
async def call_agent_async(
runner: Runner, user_id: str, session_id: str, prompt: str
) -> str:
"""Call the agent asynchronously with the user's prompt."""
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 event.content and event.content.parts:
if text := "".join(part.text or "" for part in event.content.parts):
if event.author != "user":
final_response_text += text
return final_response_text