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
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f46b73b0cb | |||
| b2f319f440 | |||
| 0c4054200f | |||
| c9e265551a | |||
| 416dc6feed | |||
| 9df3f725bd | |||
| 6c999caa41 | |||
| 77f44a4e45 | |||
| 484b33ef10 | |||
| aaf1f9b930 | |||
| 8e438f2752 | |||
| fa110c22f2 | |||
| 088200072f | |||
| bf47a8bf7d | |||
| cf5d7016a0 | |||
| f38c08b305 | |||
| 3ae6ce10bc | |||
| 13f98c396a | |||
| be7120831a | |||
| 4942f19f3f | |||
| 8488ff052f | |||
| a09781142a | |||
| 309a656f49 | |||
| 9abb8414da | |||
| 3b1f2ae9bf | |||
| 6ed635190c | |||
| d1b058707e | |||
| 54367dcc56 | |||
| fe1de7b103 | |||
| 078ac842d7 | |||
| 4b1c218cbe | |||
| 7dbd8c6a5b | |||
| b7ebb6947f | |||
| 4269cac953 | |||
| 19dbe244e0 | |||
| c224626ae1 | |||
| 1d8d1e0a4e | |||
| 0d232588e6 | |||
| cf689fd528 | |||
| 78bfce4d89 |
@@ -0,0 +1,113 @@
|
||||
# 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.
|
||||
|
||||
name: "Check file contents"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.py'
|
||||
|
||||
jobs:
|
||||
check-file-contents:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Check for logger pattern in all changed Python files
|
||||
run: |
|
||||
git fetch origin ${{ github.base_ref }}
|
||||
CHANGED_FILES=$(git diff --diff-filter=ACMR --name-only origin/${{ github.base_ref }}...HEAD | grep -E '\.py$' || true)
|
||||
if [ -n "$CHANGED_FILES" ]; then
|
||||
echo "Changed Python files to check:"
|
||||
echo "$CHANGED_FILES"
|
||||
echo ""
|
||||
|
||||
# Check for 'logger = logging.getLogger(__name__)' in changed .py files.
|
||||
# The grep command will exit with a non-zero status code if the pattern is not found.
|
||||
# We invert the exit code with ! so the step succeeds if the pattern is NOT found.
|
||||
set +e
|
||||
FILES_WITH_FORBIDDEN_LOGGER=$(grep -lE 'logger = logging\.getLogger\(__name__\)' $CHANGED_FILES)
|
||||
GREP_EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
# grep exits with 0 if matches are found, 1 if no matches are found.
|
||||
# A non-zero exit code other than 1 indicates an error.
|
||||
if [ $GREP_EXIT_CODE -eq 0 ]; then
|
||||
echo "❌ Found forbidden use of 'logger = logging.getLogger(__name__)'. Please use 'logger = logging.getLogger('google_adk.' + __name__)' instead."
|
||||
echo "The following files contain the forbidden pattern:"
|
||||
echo "$FILES_WITH_FORBIDDEN_LOGGER"
|
||||
exit 1
|
||||
elif [ $GREP_EXIT_CODE -eq 1 ]; then
|
||||
echo "✅ No instances of 'logger = logging.getLogger(__name__)' found in changed Python files."
|
||||
fi
|
||||
else
|
||||
echo "✅ No relevant Python files found."
|
||||
fi
|
||||
|
||||
- name: Check for import pattern in certain changed Python files
|
||||
run: |
|
||||
git fetch origin ${{ github.base_ref }}
|
||||
CHANGED_FILES=$(git diff --diff-filter=ACMR --name-only origin/${{ github.base_ref }}...HEAD | grep -E '\.py$' | grep -v -E '__init__.py$|version.py$|tests/.*|contributing/samples/' || true)
|
||||
if [ -n "$CHANGED_FILES" ]; then
|
||||
echo "Changed Python files to check:"
|
||||
echo "$CHANGED_FILES"
|
||||
echo ""
|
||||
|
||||
# Use grep -L to find files that DO NOT contain the pattern.
|
||||
# This command will output a list of non-compliant files.
|
||||
FILES_MISSING_IMPORT=$(grep -L 'from __future__ import annotations' $CHANGED_FILES)
|
||||
|
||||
# Check if the list of non-compliant files is empty
|
||||
if [ -z "$FILES_MISSING_IMPORT" ]; then
|
||||
echo "✅ All modified Python files include 'from __future__ import annotations'."
|
||||
exit 0
|
||||
else
|
||||
echo "❌ The following files are missing 'from __future__ import annotations':"
|
||||
echo "$FILES_MISSING_IMPORT"
|
||||
echo "This import is required to allow forward references in type annotations without quotes."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "✅ No relevant Python files found."
|
||||
fi
|
||||
|
||||
- name: Check for import from cli package in certain changed Python files
|
||||
run: |
|
||||
git fetch origin ${{ github.base_ref }}
|
||||
CHANGED_FILES=$(git diff --diff-filter=ACMR --name-only origin/${{ github.base_ref }}...HEAD | grep -E '\.py$' | grep -v -E 'cli/.*|tests/.*|contributing/samples/' || true)
|
||||
if [ -n "$CHANGED_FILES" ]; then
|
||||
echo "Changed Python files to check:"
|
||||
echo "$CHANGED_FILES"
|
||||
echo ""
|
||||
|
||||
set +e
|
||||
FILES_WITH_FORBIDDEN_IMPORT=$(grep -lE '^from.*cli.*import.*$' $CHANGED_FILES)
|
||||
GREP_EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
if [[ $GREP_EXIT_CODE -eq 0 ]]; then
|
||||
echo "❌ Do not import from the cli package outside of the cli package. If you need to reuse the code elsewhere, please move the code outside of the cli package."
|
||||
echo "The following files contain the forbidden pattern:"
|
||||
echo "$FILES_WITH_FORBIDDEN_IMPORT"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ No instances of importing from the cli package found in relevant changed Python files."
|
||||
fi
|
||||
else
|
||||
echo "✅ No relevant Python files found."
|
||||
fi
|
||||
@@ -1,5 +1,53 @@
|
||||
# Changelog
|
||||
|
||||
## [1.3.0](https://github.com/google/adk-python/compare/v1.2.1...v1.3.0) (2025-06-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add memory_service option to CLI ([416dc6f](https://github.com/google/adk-python/commit/416dc6feed26e55586d28f8c5132b31413834c88))
|
||||
* Add support for display_name and description when deploying to agent engine ([aaf1f9b](https://github.com/google/adk-python/commit/aaf1f9b930d12657bfc9b9d0abd8e2248c1fc469))
|
||||
* Dev UI: Trace View
|
||||
* New trace tab which contains all traces grouped by user messages
|
||||
* Click each row will open corresponding event details
|
||||
* Hover each row will highlight the corresponding message in dialog
|
||||
* Dev UI: Evaluation
|
||||
* Evaluation Configuration: users can now configure custom threshold for the metrics used for each eval run ([d1b0587](https://github.com/google/adk-python/commit/d1b058707eed72fd4987d8ec8f3b47941a9f7d64))
|
||||
* Each eval case added can now be viewed and edited. Right now we only support edit of text.
|
||||
* Show the used metric in evaluation history ([6ed6351](https://github.com/google/adk-python/commit/6ed635190c86d5b2ba0409064cf7bcd797fd08da))
|
||||
* Tool enhancements:
|
||||
* Add url_context_tool ([fe1de7b](https://github.com/google/adk-python/commit/fe1de7b10326a38e0d5943d7002ac7889c161826))
|
||||
* Support to customize timeout for mcpstdio connections ([54367dc](https://github.com/google/adk-python/commit/54367dcc567a2b00e80368ea753a4fc0550e5b57))
|
||||
* Introduce write protected mode to BigQuery tools ([6c999ca](https://github.com/google/adk-python/commit/6c999caa41dca3a6ec146ea42b0a794b14238ec2))
|
||||
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Agent Engine deployment:
|
||||
* Correct help text formatting for `adk deploy agent_engine` ([13f98c3](https://github.com/google/adk-python/commit/13f98c396a2fa21747e455bb5eed503a553b5b22))
|
||||
* Handle project and location in the .env properly when deploying to Agent Engine ([0c40542](https://github.com/google/adk-python/commit/0c4054200fd50041f0dce4b1c8e56292b99a8ea8))
|
||||
* Fix broken agent graphs ([3b1f2ae](https://github.com/google/adk-python/commit/3b1f2ae9bfdb632b52e6460fc5b7c9e04748bd50))
|
||||
* Forward `__annotations__` to the fake func for FunctionTool inspection ([9abb841](https://github.com/google/adk-python/commit/9abb8414da1055ab2f130194b986803779cd5cc5))
|
||||
* Handle the case when agent loading error doesn't have msg attribute in agent loader ([c224626](https://github.com/google/adk-python/commit/c224626ae189d02e5c410959b3631f6bd4d4d5c1))
|
||||
* Prevent agent_graph.py throwing when workflow agent is root agent ([4b1c218](https://github.com/google/adk-python/commit/4b1c218cbe69f7fb309b5a223aa2487b7c196038))
|
||||
* Remove display_name for non-Vertex file uploads ([cf5d701](https://github.com/google/adk-python/commit/cf5d7016a0a6ccf2b522df6f2d608774803b6be4))
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* Add DeepWiki badge to README ([f38c08b](https://github.com/google/adk-python/commit/f38c08b3057b081859178d44fa2832bed46561a9))
|
||||
* Update code example in tool declaration to reflect BigQuery artifact description ([3ae6ce1](https://github.com/google/adk-python/commit/3ae6ce10bc5a120c48d84045328c5d78f6eb85d4))
|
||||
|
||||
|
||||
## [1.2.1](https://github.com/google/adk-python/compare/v1.2.0...v1.2.1) (2025-06-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Import deprecated from typing_extensions ([068df04](https://github.com/google/adk-python/commit/068df04bcef694725dd36e09f4476b5e67f1b456))
|
||||
|
||||
|
||||
## [1.2.0](https://github.com/google/adk-python/compare/v1.1.1...v1.2.0) (2025-06-04)
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
[](LICENSE)
|
||||
[](https://github.com/google/adk-python/actions/workflows/python-unit-tests.yml)
|
||||
[](https://www.reddit.com/r/agentdevelopmentkit/)
|
||||
[](https://deepwiki.com/google/adk-python)
|
||||
|
||||
<html>
|
||||
<h2 align="center">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This folder host resources for ADK contributors, for example, testing samples etc.
|
||||
|
||||
# Samples
|
||||
## Samples
|
||||
|
||||
Samples folder host samples to test different features. The samples are usually minimal and simplistic to test one or a few scenarios.
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
# 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 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
|
||||
if not GITHUB_TOKEN:
|
||||
raise ValueError("GITHUB_TOKEN environment variable not set")
|
||||
|
||||
# Repository information
|
||||
OWNER = "google"
|
||||
REPO = "adk-python"
|
||||
|
||||
# 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",
|
||||
}
|
||||
|
||||
|
||||
def list_issues(per_page: int):
|
||||
"""
|
||||
Generator to list all issues for the repository by handling pagination.
|
||||
|
||||
Args:
|
||||
per_page: number of pages to return per page.
|
||||
|
||||
"""
|
||||
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()
|
||||
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
|
||||
|
||||
|
||||
def add_label_to_issue(issue_number: str, label: str):
|
||||
"""
|
||||
Add the specified label to the given issue number.
|
||||
|
||||
Args:
|
||||
issue_number: issue number of the Github issue, in string foramt.
|
||||
label: label to assign
|
||||
"""
|
||||
url = f"{BASE_URL}/repos/{OWNER}/{REPO}/issues/{issue_number}/labels"
|
||||
payload = [label]
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
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.
|
||||
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 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.
|
||||
""",
|
||||
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,
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -17,11 +17,15 @@ import os
|
||||
from google.adk.agents import llm_agent
|
||||
from google.adk.tools.bigquery import BigQueryCredentialsConfig
|
||||
from google.adk.tools.bigquery import BigQueryToolset
|
||||
from google.adk.tools.bigquery.config import BigQueryToolConfig
|
||||
from google.adk.tools.bigquery.config import WriteMode
|
||||
import google.auth
|
||||
|
||||
RUN_WITH_ADC = False
|
||||
|
||||
|
||||
tool_config = BigQueryToolConfig(write_mode=WriteMode.ALLOWED)
|
||||
|
||||
if RUN_WITH_ADC:
|
||||
# Initialize the tools to use the application default credentials.
|
||||
application_default_credentials, _ = google.auth.default()
|
||||
@@ -37,7 +41,9 @@ else:
|
||||
client_secret=os.getenv("OAUTH_CLIENT_SECRET"),
|
||||
)
|
||||
|
||||
bigquery_toolset = BigQueryToolset(credentials_config=credentials_config)
|
||||
bigquery_toolset = BigQueryToolset(
|
||||
credentials_config=credentials_config, bigquery_tool_config=tool_config
|
||||
)
|
||||
|
||||
# The variable name `root_agent` determines what your root agent is for the
|
||||
# debug CLI
|
||||
|
||||
@@ -70,12 +70,22 @@ async def main():
|
||||
if event.content.parts and event.content.parts[0].text:
|
||||
print(f'** {event.author}: {event.content.parts[0].text}')
|
||||
|
||||
async def check_rolls_in_state(rolls_size: int):
|
||||
session = await runner.session_service.get_session(
|
||||
app_name=app_name, user_id=user_id_1, session_id=session_11.id
|
||||
)
|
||||
assert len(session.state['rolls']) == rolls_size
|
||||
for roll in session.state['rolls']:
|
||||
assert roll > 0 and roll <= 100
|
||||
|
||||
start_time = time.time()
|
||||
print('Start time:', start_time)
|
||||
print('------------------------------------')
|
||||
await run_prompt(session_11, 'Hi')
|
||||
await run_prompt(session_11, 'Roll a die with 100 sides')
|
||||
await check_rolls_in_state(1)
|
||||
await run_prompt(session_11, 'Roll a die again with 100 sides.')
|
||||
await check_rolls_in_state(2)
|
||||
await run_prompt(session_11, 'What numbers did I got?')
|
||||
await run_prompt_bytes(session_11, 'Hi bytes')
|
||||
print(
|
||||
|
||||
@@ -1 +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
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
import os
|
||||
|
||||
from google.adk.agents.llm_agent import LlmAgent
|
||||
from google.adk.tools.mcp_tool import StdioConnectionParams
|
||||
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
|
||||
from google.adk.tools.mcp_tool.mcp_toolset import StdioServerParameters
|
||||
from mcp import StdioServerParameters
|
||||
|
||||
_allowed_path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
@@ -31,13 +32,16 @@ Allowed directory: {_allowed_path}
|
||||
""",
|
||||
tools=[
|
||||
MCPToolset(
|
||||
connection_params=StdioServerParameters(
|
||||
command='npx',
|
||||
args=[
|
||||
'-y', # Arguments for the command
|
||||
'@modelcontextprotocol/server-filesystem',
|
||||
_allowed_path,
|
||||
],
|
||||
connection_params=StdioConnectionParams(
|
||||
server_params=StdioServerParameters(
|
||||
command='npx',
|
||||
args=[
|
||||
'-y', # Arguments for the command
|
||||
'@modelcontextprotocol/server-filesystem',
|
||||
_allowed_path,
|
||||
],
|
||||
),
|
||||
timeout=5,
|
||||
),
|
||||
# don't want agent to do write operation
|
||||
# you can also do below
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# 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 os
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from google.adk.agents import Agent
|
||||
from google.adk.tools.retrieval.vertex_ai_rag_retrieval import VertexAiRagRetrieval
|
||||
from vertexai.preview import rag
|
||||
|
||||
load_dotenv()
|
||||
|
||||
ask_vertex_retrieval = VertexAiRagRetrieval(
|
||||
name="retrieve_rag_documentation",
|
||||
description=(
|
||||
"Use this tool to retrieve documentation and reference materials for"
|
||||
" the question from the RAG corpus,"
|
||||
),
|
||||
rag_resources=[
|
||||
rag.RagResource(
|
||||
# please fill in your own rag corpus
|
||||
# e.g. projects/123/locations/us-central1/ragCorpora/456
|
||||
rag_corpus=os.environ.get("RAG_CORPUS"),
|
||||
)
|
||||
],
|
||||
similarity_top_k=1,
|
||||
vector_distance_threshold=0.6,
|
||||
)
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-2.0-flash-001",
|
||||
name="root_agent",
|
||||
instruction=(
|
||||
"You are an AI assistant with access to specialized corpus of"
|
||||
" documents. Your role is to provide accurate and concise answers to"
|
||||
" questions based on documents that are retrievable using"
|
||||
" ask_vertex_retrieval."
|
||||
),
|
||||
tools=[ask_vertex_retrieval],
|
||||
)
|
||||
@@ -1 +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
|
||||
|
||||
@@ -129,7 +129,7 @@ class LlmAgent(BaseAgent):
|
||||
global_instruction: Union[str, InstructionProvider] = ''
|
||||
"""Instructions for all the agents in the entire agent tree.
|
||||
|
||||
global_instruction ONLY takes effect in root agent.
|
||||
ONLY the global_instruction in root agent will take effect.
|
||||
|
||||
For example: use global_instruction to make all agents have a stable identity
|
||||
or personality.
|
||||
@@ -204,11 +204,6 @@ class LlmAgent(BaseAgent):
|
||||
"""
|
||||
# Advance features - End
|
||||
|
||||
# TODO: remove below fields after migration. - Start
|
||||
# These fields are added back for easier migration.
|
||||
examples: Optional[ExamplesUnion] = None
|
||||
# TODO: remove above fields after migration. - End
|
||||
|
||||
# Callbacks - Start
|
||||
before_model_callback: Optional[BeforeModelCallback] = None
|
||||
"""Callback or list of callbacks to be called before calling the LLM.
|
||||
|
||||
@@ -112,7 +112,7 @@ class AuthHandler:
|
||||
|
||||
def parse_and_store_auth_response(self, state: State) -> None:
|
||||
|
||||
credential_key = self.get_credential_key()
|
||||
credential_key = "temp:" + self.auth_config.get_credential_key()
|
||||
|
||||
state[credential_key] = self.auth_config.exchanged_auth_credential
|
||||
if not isinstance(
|
||||
@@ -130,7 +130,7 @@ class AuthHandler:
|
||||
raise ValueError("auth_scheme is empty.")
|
||||
|
||||
def get_auth_response(self, state: State) -> AuthCredential:
|
||||
credential_key = self.get_credential_key()
|
||||
credential_key = "temp:" + self.auth_config.get_credential_key()
|
||||
return state.get(credential_key, None)
|
||||
|
||||
def generate_auth_request(self) -> AuthConfig:
|
||||
@@ -192,29 +192,6 @@ class AuthHandler:
|
||||
exchanged_auth_credential=exchanged_credential,
|
||||
)
|
||||
|
||||
def get_credential_key(self) -> str:
|
||||
"""Generates a unique key for the given auth scheme and credential."""
|
||||
auth_scheme = self.auth_config.auth_scheme
|
||||
auth_credential = self.auth_config.raw_auth_credential
|
||||
if auth_scheme.model_extra:
|
||||
auth_scheme = auth_scheme.model_copy(deep=True)
|
||||
auth_scheme.model_extra.clear()
|
||||
scheme_name = (
|
||||
f"{auth_scheme.type_.name}_{hash(auth_scheme.model_dump_json())}"
|
||||
if auth_scheme
|
||||
else ""
|
||||
)
|
||||
if auth_credential.model_extra:
|
||||
auth_credential = auth_credential.model_copy(deep=True)
|
||||
auth_credential.model_extra.clear()
|
||||
credential_name = (
|
||||
f"{auth_credential.auth_type.value}_{hash(auth_credential.model_dump_json())}"
|
||||
if auth_credential
|
||||
else ""
|
||||
)
|
||||
|
||||
return f"temp:adk_{scheme_name}_{credential_name}"
|
||||
|
||||
def generate_auth_uri(
|
||||
self,
|
||||
) -> AuthCredential:
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .auth_credential import AuthCredential
|
||||
from .auth_credential import BaseModelWithConfig
|
||||
from .auth_schemes import AuthScheme
|
||||
@@ -43,6 +45,34 @@ class AuthConfig(BaseModelWithConfig):
|
||||
this field to guide the user through the OAuth2 flow and fill auth response in
|
||||
this field"""
|
||||
|
||||
def get_credential_key(self):
|
||||
"""Generates a hash key based on auth_scheme and raw_auth_credential. This
|
||||
hash key can be used to store / retrieve exchanged_auth_credential in a
|
||||
credentials store.
|
||||
"""
|
||||
auth_scheme = self.auth_scheme
|
||||
|
||||
if auth_scheme.model_extra:
|
||||
auth_scheme = auth_scheme.model_copy(deep=True)
|
||||
auth_scheme.model_extra.clear()
|
||||
scheme_name = (
|
||||
f"{auth_scheme.type_.name}_{hash(auth_scheme.model_dump_json())}"
|
||||
if auth_scheme
|
||||
else ""
|
||||
)
|
||||
|
||||
auth_credential = self.raw_auth_credential
|
||||
if auth_credential.model_extra:
|
||||
auth_credential = auth_credential.model_copy(deep=True)
|
||||
auth_credential.model_extra.clear()
|
||||
credential_name = (
|
||||
f"{auth_credential.auth_type.value}_{hash(auth_credential.model_dump_json())}"
|
||||
if auth_credential
|
||||
else ""
|
||||
)
|
||||
|
||||
return f"adk_{scheme_name}_{credential_name}"
|
||||
|
||||
|
||||
class AuthToolArguments(BaseModelWithConfig):
|
||||
"""the arguments for the special long running function tool that is used to
|
||||
|
||||
@@ -64,11 +64,11 @@ async def build_graph(
|
||||
if isinstance(tool_or_agent, BaseAgent):
|
||||
# Added Workflow Agent checks for different agent types
|
||||
if isinstance(tool_or_agent, SequentialAgent):
|
||||
return tool_or_agent.name + f' (Sequential Agent)'
|
||||
return tool_or_agent.name + ' (Sequential Agent)'
|
||||
elif isinstance(tool_or_agent, LoopAgent):
|
||||
return tool_or_agent.name + f' (Loop Agent)'
|
||||
return tool_or_agent.name + ' (Loop Agent)'
|
||||
elif isinstance(tool_or_agent, ParallelAgent):
|
||||
return tool_or_agent.name + f' (Parallel Agent)'
|
||||
return tool_or_agent.name + ' (Parallel Agent)'
|
||||
else:
|
||||
return tool_or_agent.name
|
||||
elif isinstance(tool_or_agent, BaseTool):
|
||||
@@ -144,49 +144,53 @@ async def build_graph(
|
||||
)
|
||||
return False
|
||||
|
||||
def build_cluster(child: graphviz.Digraph, agent: BaseAgent, name: str):
|
||||
async def build_cluster(child: graphviz.Digraph, agent: BaseAgent, name: str):
|
||||
if isinstance(agent, LoopAgent):
|
||||
# Draw the edge from the parent agent to the first sub-agent
|
||||
draw_edge(parent_agent.name, agent.sub_agents[0].name)
|
||||
if parent_agent:
|
||||
draw_edge(parent_agent.name, agent.sub_agents[0].name)
|
||||
length = len(agent.sub_agents)
|
||||
currLength = 0
|
||||
curr_length = 0
|
||||
# Draw the edges between the sub-agents
|
||||
for sub_agent_int_sequential in agent.sub_agents:
|
||||
build_graph(child, sub_agent_int_sequential, highlight_pairs)
|
||||
await build_graph(child, sub_agent_int_sequential, highlight_pairs)
|
||||
# Draw the edge between the current sub-agent and the next one
|
||||
# If it's the last sub-agent, draw an edge to the first one to indicating a loop
|
||||
draw_edge(
|
||||
agent.sub_agents[currLength].name,
|
||||
agent.sub_agents[curr_length].name,
|
||||
agent.sub_agents[
|
||||
0 if currLength == length - 1 else currLength + 1
|
||||
0 if curr_length == length - 1 else curr_length + 1
|
||||
].name,
|
||||
)
|
||||
currLength += 1
|
||||
curr_length += 1
|
||||
elif isinstance(agent, SequentialAgent):
|
||||
# Draw the edge from the parent agent to the first sub-agent
|
||||
draw_edge(parent_agent.name, agent.sub_agents[0].name)
|
||||
if parent_agent:
|
||||
draw_edge(parent_agent.name, agent.sub_agents[0].name)
|
||||
length = len(agent.sub_agents)
|
||||
currLength = 0
|
||||
curr_length = 0
|
||||
|
||||
# Draw the edges between the sub-agents
|
||||
for sub_agent_int_sequential in agent.sub_agents:
|
||||
build_graph(child, sub_agent_int_sequential, highlight_pairs)
|
||||
await build_graph(child, sub_agent_int_sequential, highlight_pairs)
|
||||
# Draw the edge between the current sub-agent and the next one
|
||||
# If it's the last sub-agent, don't draw an edge to avoid a loop
|
||||
draw_edge(
|
||||
agent.sub_agents[currLength].name,
|
||||
agent.sub_agents[currLength + 1].name,
|
||||
) if currLength != length - 1 else None
|
||||
currLength += 1
|
||||
if curr_length != length - 1:
|
||||
draw_edge(
|
||||
agent.sub_agents[curr_length].name,
|
||||
agent.sub_agents[curr_length + 1].name,
|
||||
)
|
||||
curr_length += 1
|
||||
|
||||
elif isinstance(agent, ParallelAgent):
|
||||
# Draw the edge from the parent agent to every sub-agent
|
||||
for sub_agent in agent.sub_agents:
|
||||
build_graph(child, sub_agent, highlight_pairs)
|
||||
draw_edge(parent_agent.name, sub_agent.name)
|
||||
await build_graph(child, sub_agent, highlight_pairs)
|
||||
if parent_agent:
|
||||
draw_edge(parent_agent.name, sub_agent.name)
|
||||
else:
|
||||
for sub_agent in agent.sub_agents:
|
||||
build_graph(child, sub_agent, highlight_pairs)
|
||||
await build_graph(child, sub_agent, highlight_pairs)
|
||||
draw_edge(agent.name, sub_agent.name)
|
||||
|
||||
child.attr(
|
||||
@@ -196,21 +200,20 @@ async def build_graph(
|
||||
fontcolor=light_gray,
|
||||
)
|
||||
|
||||
def draw_node(tool_or_agent: Union[BaseAgent, BaseTool]):
|
||||
async def draw_node(tool_or_agent: Union[BaseAgent, BaseTool]):
|
||||
name = get_node_name(tool_or_agent)
|
||||
shape = get_node_shape(tool_or_agent)
|
||||
caption = get_node_caption(tool_or_agent)
|
||||
asCluster = should_build_agent_cluster(tool_or_agent)
|
||||
child = None
|
||||
as_cluster = should_build_agent_cluster(tool_or_agent)
|
||||
if highlight_pairs:
|
||||
for highlight_tuple in highlight_pairs:
|
||||
if name in highlight_tuple:
|
||||
# if in highlight, draw highlight node
|
||||
if asCluster:
|
||||
if as_cluster:
|
||||
cluster = graphviz.Digraph(
|
||||
name='cluster_' + name
|
||||
) # adding "cluster_" to the name makes the graph render as a cluster subgraph
|
||||
build_cluster(cluster, agent, name)
|
||||
await build_cluster(cluster, agent, name)
|
||||
graph.subgraph(cluster)
|
||||
else:
|
||||
graph.node(
|
||||
@@ -224,12 +227,12 @@ async def build_graph(
|
||||
)
|
||||
return
|
||||
# if not in highlight, draw non-highlight node
|
||||
if asCluster:
|
||||
if as_cluster:
|
||||
|
||||
cluster = graphviz.Digraph(
|
||||
name='cluster_' + name
|
||||
) # adding "cluster_" to the name makes the graph render as a cluster subgraph
|
||||
build_cluster(cluster, agent, name)
|
||||
await build_cluster(cluster, agent, name)
|
||||
graph.subgraph(cluster)
|
||||
|
||||
else:
|
||||
@@ -264,10 +267,9 @@ async def build_graph(
|
||||
else:
|
||||
graph.edge(from_name, to_name, arrowhead='none', color=light_gray)
|
||||
|
||||
draw_node(agent)
|
||||
await draw_node(agent)
|
||||
for sub_agent in agent.sub_agents:
|
||||
|
||||
build_graph(graph, sub_agent, highlight_pairs, agent)
|
||||
await build_graph(graph, sub_agent, highlight_pairs, agent)
|
||||
if not should_build_agent_cluster(
|
||||
sub_agent
|
||||
) and not should_build_agent_cluster(
|
||||
@@ -276,7 +278,7 @@ async def build_graph(
|
||||
draw_edge(agent.name, sub_agent.name)
|
||||
if isinstance(agent, LlmAgent):
|
||||
for tool in await agent.canonical_tools():
|
||||
draw_node(tool)
|
||||
await draw_node(tool)
|
||||
draw_edge(agent.name, get_node_name(tool))
|
||||
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user