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
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29c1115959 | |||
| 60e314a78f | |||
| ee9b4ca908 | |||
| db711cd789 | |||
| 055dfc7974 | |||
| df8684734b | |||
| 9cccab4537 | |||
| e1a7593ae8 | |||
| cb3244bb58 | |||
| 894d8c6c26 | |||
| 99f893ae28 | |||
| 7b2fe14dab | |||
| 68d70488b9 | |||
| c6320caaa5 | |||
| f0bdcaba44 | |||
| 7b356ddc1b | |||
| 4111f85b61 | |||
| b23deeb9ab | |||
| 51a638b6b8 | |||
| 6388ba3b20 | |||
| 4f54660d6d | |||
| ee743bd19a | |||
| bab57296d5 | |||
| 1ae944b39d | |||
| 32e87f6381 | |||
| 56775afc48 | |||
| cde7f7c243 | |||
| 82e6623fa9 | |||
| 143ad44f8c | |||
| f22bac0b20 | |||
| de841a4a09 | |||
| b7ce5e17b6 | |||
| 9d2388a46f | |||
| 711df01e73 | |||
| a9b853fe36 | |||
| 3d444cc953 | |||
| 82bd4f380b | |||
| c557b0a1f2 | |||
| f2735177f1 | |||
| 2b64715505 | |||
| 0a07a667e9 | |||
| e9182e5eb4 | |||
| b0c3cc6e36 | |||
| bb8a269079 | |||
| 507424acb9 | |||
| 92821b8dda |
@@ -101,6 +101,7 @@ Thumbs.db
|
||||
|
||||
# AI Coding Tools - Project-specific configs
|
||||
# Developers should symlink or copy AGENTS.md and add their own overrides locally
|
||||
.adk/
|
||||
.claude/
|
||||
CLAUDE.md
|
||||
.cursor/
|
||||
|
||||
@@ -1,5 +1,75 @@
|
||||
# Changelog
|
||||
|
||||
## [1.21.0](https://github.com/google/adk-python/compare/v1.20.0...v1.21.0) (2025-12-11)
|
||||
|
||||
### Features
|
||||
* **[Interactions API Support]**
|
||||
* The newly released Gemini [Interactions API](https://ai.google.dev/gemini-api/docs/interactions) is supported in ADK Now. To use it:
|
||||
```Python
|
||||
Agent(
|
||||
model=Gemini(
|
||||
model="gemini-3-pro-preview",
|
||||
use_interactions_api=True,
|
||||
),
|
||||
name="...",
|
||||
description="...",
|
||||
instruction="...",
|
||||
)
|
||||
```
|
||||
see [samples](https://github.com/google/adk-python/tree/main/contributing/samples/interactions_api) for details
|
||||
|
||||
|
||||
* **[Services]**
|
||||
* Add `add_session_to_memory` to `CallbackContext` and `ToolContext` to explicitly save the current session to memory ([7b356dd](https://github.com/google/adk-python/commit/7b356ddc1b1694d2c8a9eee538f3a41cf5518e42))
|
||||
|
||||
* **[Plugins]**
|
||||
* Add location for table in agent events in plugin BigQueryAgentAnalytics ([507424a](https://github.com/google/adk-python/commit/507424acb9aabc697fc64ef2e9a57875f25f0a21))
|
||||
* Upgrade BigQueryAgentAnalyticsPlugin to v2.0 with improved performance, multimodal support, and reliability ([7b2fe14](https://github.com/google/adk-python/commit/7b2fe14dab96440ee25b66dae9e66eadba629a56))
|
||||
|
||||
|
||||
* **[A2A]**
|
||||
* Adds ADK EventActions to A2A response ([32e87f6](https://github.com/google/adk-python/commit/32e87f6381ff8905a06a9a43a0207d758a74299d))
|
||||
|
||||
* **[Tools]**
|
||||
* Add `header_provider` to `OpenAPIToolset` and `RestApiTool` ([e1a7593](https://github.com/google/adk-python/commit/e1a7593ae8455d51cdde46f5165410217400d3c9))
|
||||
* Allow overriding connection template ([cde7f7c](https://github.com/google/adk-python/commit/cde7f7c243a7cdc8c7b886f68be55fd59b1f6d5a))
|
||||
* Add SSL certificate verification configuration to OpenAPI tools using the `verify` parameter ([9d2388a](https://github.com/google/adk-python/commit/9d2388a46f7a481ea1ec522f33641a06c64394ed))
|
||||
* Use json schema for function tool declaration when feature enabled ([cb3244b](https://github.com/google/adk-python/commit/cb3244bb58904ab508f77069b436f85b442d3299))
|
||||
|
||||
* **[Models]**
|
||||
* Add Gemma3Ollama model integration and a sample ([e9182e5](https://github.com/google/adk-python/commit/e9182e5eb4a37fb5219fc607cd8f06d7e6982e83))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Install dependencies for py 3.10 ([9cccab4](https://github.com/google/adk-python/commit/9cccab453706138826f313c47118812133e099c4))
|
||||
* Refactor LiteLLM response schema formatting for different models ([894d8c6](https://github.com/google/adk-python/commit/894d8c6c2652492324c428e8dae68a8646b17485))
|
||||
* Resolve project and credentials before creating Spanner client ([99f893a](https://github.com/google/adk-python/commit/99f893ae282a04c67cce5f80e87d3bfadd3943e6))
|
||||
* Avoid false positive "App name mismatch" warnings in Runner ([6388ba3](https://github.com/google/adk-python/commit/6388ba3b2054e60d218eae6ec8abc621ed0a1139))
|
||||
* Update the code to work with either 1 event or more than 1 events ([4f54660](https://github.com/google/adk-python/commit/4f54660d6de54ddde0fec6e09fdd68890ce657ca))
|
||||
* OpenAPI schema generation by skipping JSON schema for judge_model_config ([56775af](https://github.com/google/adk-python/commit/56775afc48ee54e9cbea441a6e0fa6c8a12891b9))
|
||||
* Add tool_name_prefix support to OpenAPIToolset ([82e6623](https://github.com/google/adk-python/commit/82e6623fa97fb9cbc6893b44e228f4da098498da))
|
||||
* Pass context to client interceptors ([143ad44](https://github.com/google/adk-python/commit/143ad44f8c5d1c56fc92dd691589aaa0b788e485))
|
||||
* Yield event with error code when agent run raised A2AClientHTTPError ([b7ce5e1](https://github.com/google/adk-python/commit/b7ce5e17b6653074c5b41d08b2027b5e9970a671))
|
||||
* Handle string function responses in LiteLLM conversion ([2b64715](https://github.com/google/adk-python/commit/2b6471550591ee7fc5f70f79e66a6e4080df442b))
|
||||
* ApigeeLLM support for Built-in tools like GoogleSearch, BuiltInCodeExecutor when calling Gemini models through Apigee ([a9b853f](https://github.com/google/adk-python/commit/a9b853fe364d08703b37914a89cf02293b5c553b))
|
||||
* Extract and propagate task_id in RemoteA2aAgent ([82bd4f3](https://github.com/google/adk-python/commit/82bd4f380bd8b4822191ea16e6140fe2613023ad))
|
||||
* Update FastAPI and Starlette to fix CVE-2025-62727 (ReDoS vulnerability) ([c557b0a](https://github.com/google/adk-python/commit/c557b0a1f2aac9f0ef7f1e0f65e3884007407e30))
|
||||
* Add client id to token exchange ([f273517](https://github.com/google/adk-python/commit/f2735177f195b8d7745dba6360688ddfebfed31a))
|
||||
|
||||
### Improvements
|
||||
|
||||
* Normalize multipart content for LiteLLM's ollama_chat provider ([055dfc7](https://github.com/google/adk-python/commit/055dfc79747aa365db8441908d4994f795e94a68))
|
||||
* Update adk web, fixes image not rendering, state not updating, update drop down box width and trace icons ([df86847](https://github.com/google/adk-python/commit/df8684734bbfd5a8afe3b4362574fe93dcb43048))
|
||||
* Add sample agent for interaction api integration ([68d7048](https://github.com/google/adk-python/commit/68d70488b9340251a9d37e8ae3a9166870f26aa1))
|
||||
* Update genAI SDK version ([f0bdcab](https://github.com/google/adk-python/commit/f0bdcaba449f21bd8c27cde7dbedc03bf5ec5349))
|
||||
* Introduce `build_function_declaration_with_json_schema` to use pydantic to generate json schema for FunctionTool ([51a638b](https://github.com/google/adk-python/commit/51a638b6b85943d4aaec4ee37c95a55386ebac90))
|
||||
* Update component definition for triaging agent ([ee743bd](https://github.com/google/adk-python/commit/ee743bd19a8134129111fc4769ec24e40a611982))
|
||||
* Migrate Google tools to use the new feature decorator ([bab5729](https://github.com/google/adk-python/commit/bab57296d553cb211106ece9ee2c226c64a60c57))
|
||||
* Migrate computer to use the new feature decorator ([1ae944b](https://github.com/google/adk-python/commit/1ae944b39d9cf263e15b36c76480975fe4291d22))
|
||||
* Add Spanner execute sql query result mode using list of dictionaries ([f22bac0](https://github.com/google/adk-python/commit/f22bac0b202cd8f273bf2dee9fff57be1b40730d))
|
||||
* Improve error message for missing `invocation_id` and `new_message` in `run_async` ([de841a4](https://github.com/google/adk-python/commit/de841a4a0982d98ade4478f10481c817a923faa2))
|
||||
|
||||
## [1.20.0](https://github.com/google/adk-python/compare/v1.19.0...v1.20.0) (2025-12-01)
|
||||
|
||||
|
||||
|
||||
@@ -24,13 +24,14 @@ from adk_documentation.settings import DOC_OWNER
|
||||
from adk_documentation.settings import DOC_REPO
|
||||
from adk_documentation.tools import get_issue
|
||||
from adk_documentation.utils import call_agent_async
|
||||
from adk_documentation.utils import parse_suggestions
|
||||
from google.adk.cli.utils import logs
|
||||
from google.adk.runners import InMemoryRunner
|
||||
|
||||
APP_NAME = "adk_docs_updater"
|
||||
USER_ID = "adk_docs_updater_user"
|
||||
|
||||
logs.setup_adk_logger(level=logging.DEBUG)
|
||||
logs.setup_adk_logger(level=logging.INFO)
|
||||
|
||||
|
||||
def process_arguments():
|
||||
@@ -68,23 +69,84 @@ async def main():
|
||||
print(f"Failed to get issue {issue_number}: {get_issue_response}\n")
|
||||
return
|
||||
issue = get_issue_response["issue"]
|
||||
issue_title = issue.get("title", "")
|
||||
issue_body = issue.get("body", "")
|
||||
|
||||
# Parse numbered suggestions from issue body
|
||||
suggestions = parse_suggestions(issue_body)
|
||||
|
||||
if not suggestions:
|
||||
print(f"No numbered suggestions found in issue #{issue_number}.")
|
||||
print("Falling back to processing the entire issue as a single task.")
|
||||
suggestions = [(1, issue_body)]
|
||||
|
||||
print(f"Found {len(suggestions)} suggestion(s) in issue #{issue_number}.")
|
||||
print("=" * 80)
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
response = await call_agent_async(
|
||||
runner,
|
||||
USER_ID,
|
||||
session.id,
|
||||
f"Please update the ADK docs according to the following issue:\n{issue}",
|
||||
results = []
|
||||
for suggestion_num, suggestion_text in suggestions:
|
||||
print(f"\n>>> Processing suggestion #{suggestion_num}...")
|
||||
print("-" * 80)
|
||||
|
||||
# Create a new session for each suggestion to avoid context interference
|
||||
session = await runner.session_service.create_session(
|
||||
app_name=APP_NAME,
|
||||
user_id=USER_ID,
|
||||
)
|
||||
|
||||
prompt = f"""
|
||||
Please update the ADK docs according to suggestion #{suggestion_num} from issue #{issue_number}.
|
||||
|
||||
Issue title: {issue_title}
|
||||
|
||||
Suggestion to process:
|
||||
{suggestion_text}
|
||||
|
||||
Note: Focus only on this specific suggestion. Create exactly one pull request for this suggestion.
|
||||
"""
|
||||
|
||||
try:
|
||||
response = await call_agent_async(
|
||||
runner,
|
||||
USER_ID,
|
||||
session.id,
|
||||
prompt,
|
||||
)
|
||||
results.append({
|
||||
"suggestion_num": suggestion_num,
|
||||
"status": "success",
|
||||
"response": response,
|
||||
})
|
||||
print(f"<<<< Suggestion #{suggestion_num} completed.")
|
||||
except Exception as e:
|
||||
results.append({
|
||||
"suggestion_num": suggestion_num,
|
||||
"status": "error",
|
||||
"error": str(e),
|
||||
})
|
||||
print(f"<<<< Suggestion #{suggestion_num} failed: {e}")
|
||||
|
||||
print("-" * 80)
|
||||
|
||||
# Print summary
|
||||
print("\n" + "=" * 80)
|
||||
print("SUMMARY")
|
||||
print("=" * 80)
|
||||
successful = [r for r in results if r["status"] == "success"]
|
||||
failed = [r for r in results if r["status"] == "error"]
|
||||
print(
|
||||
f"Total: {len(results)}, Success: {len(successful)}, Failed:"
|
||||
f" {len(failed)}"
|
||||
)
|
||||
print(f"<<<< Agent Final Output: {response}\n")
|
||||
if failed:
|
||||
print("\nFailed suggestions:")
|
||||
for r in failed:
|
||||
print(f" - Suggestion #{r['suggestion_num']}: {r['error']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
|
||||
from adk_documentation.settings import GITHUB_TOKEN
|
||||
from google.adk.agents.run_config import RunConfig
|
||||
@@ -96,3 +98,47 @@ async def call_agent_async(
|
||||
final_response_text += text
|
||||
|
||||
return final_response_text
|
||||
|
||||
|
||||
def parse_suggestions(issue_body: str) -> List[Tuple[int, str]]:
|
||||
"""Parse numbered suggestions from issue body.
|
||||
|
||||
Supports multiple formats:
|
||||
- Format A (markdown headers): "### 1. Title"
|
||||
- Format B (numbered list with bold): "1. **Title**"
|
||||
|
||||
Args:
|
||||
issue_body: The body text of the GitHub issue.
|
||||
|
||||
Returns:
|
||||
A list of tuples, where each tuple contains:
|
||||
- The suggestion number (1-based)
|
||||
- The full text of that suggestion
|
||||
"""
|
||||
# Try different patterns in order of preference
|
||||
patterns = [
|
||||
# Format A: "### 1. Title" (markdown header with number)
|
||||
(r"(?=^###\s+\d+\.)", r"^###\s+(\d+)\."),
|
||||
# Format B: "1. **Title**" (numbered list with bold)
|
||||
(r"(?=^\d+\.\s+\*\*)", r"^(\d+)\.\s+\*\*"),
|
||||
]
|
||||
|
||||
for split_pattern, match_pattern in patterns:
|
||||
parts = re.split(split_pattern, issue_body, flags=re.MULTILINE)
|
||||
|
||||
suggestions = []
|
||||
for part in parts:
|
||||
part = part.strip()
|
||||
if not part:
|
||||
continue
|
||||
|
||||
match = re.match(match_pattern, part)
|
||||
if match:
|
||||
suggestion_num = int(match.group(1))
|
||||
suggestions.append((suggestion_num, part))
|
||||
|
||||
# If we found suggestions with this pattern, return them
|
||||
if suggestions:
|
||||
return suggestions
|
||||
|
||||
return []
|
||||
|
||||
@@ -18,7 +18,7 @@ The agent performs different actions based on the issue state:
|
||||
|
||||
### Component Labels
|
||||
The agent can assign the following component labels, each mapped to an owner:
|
||||
- `core`, `tools`, `mcp`, `eval`, `live`, `models`, `tracing`, `web`, `services`, `documentation`, `question`, `agent engine`, `a2a`, `bq`
|
||||
- `a2a`, `agent engine`, `auth`, `bq`, `core`, `documentation`, `eval`, `live`, `mcp`, `models`, `services`, `tools`, `tracing`, `web`, `workflow`
|
||||
|
||||
### Issue Types
|
||||
Based on the issue content, the agent will set the issue type to:
|
||||
|
||||
@@ -58,13 +58,16 @@ LABEL_GUIDELINES = """
|
||||
- "tracing": Telemetry, observability, structured logs, or spans.
|
||||
- "core": Core ADK runtime (Agent definitions, Runner, planners,
|
||||
thinking config, CLI commands, GlobalInstructionPlugin, CPU usage, or
|
||||
general orchestration). Default to "core" when the topic is about ADK
|
||||
behavior and no other label is a better fit.
|
||||
general orchestration including agent transfer for multi-agents system).
|
||||
Default to "core" when the topic is about ADK behavior and no other
|
||||
label is a better fit.
|
||||
- "agent engine": Vertex AI Agent Engine deployment or sandbox topics
|
||||
only (e.g., `.agent_engine_config.json`, `ae_ignore`, Agent Engine
|
||||
sandbox, `agent_engine_id`). If the issue does not explicitly mention
|
||||
Agent Engine concepts, do not use this label—choose "core" instead.
|
||||
- "a2a": Agent-to-agent workflows, coordination logic, or A2A protocol.
|
||||
- "a2a": A2A protocol, running agent as a2a agent with "--a2a" option for
|
||||
remote agent to talk with. Talking to remote agent via RemoteA2aAgent.
|
||||
NOT including those local multi-agent systems.
|
||||
- "bq": BigQuery integration or general issues related to BigQuery.
|
||||
- "workflow": Workflow agents and workflow execution.
|
||||
- "auth": Authentication or authorization issues.
|
||||
@@ -253,25 +256,6 @@ root_agent = Agent(
|
||||
|
||||
{LABEL_GUIDELINES}
|
||||
|
||||
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 "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 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, Runner behavior, planners, or performance, label it with "core".
|
||||
- Use "agent engine" only when the issue clearly references Vertex AI Agent Engine deployment artifacts (for example `.agent_engine_config.json`, `ae_ignore`, `agent_engine_id`, or Agent Engine sandbox errors).
|
||||
- If it's about Model Context Protocol (e.g. MCP tool, MCP toolset, MCP session management etc.), label it with both "mcp" and "tools".
|
||||
- If it's about A2A integrations or workflows, label it with "a2a".
|
||||
- If it's about BigQuery integrations, label it with "bq".
|
||||
- If it's about workflow agents or workflow execution, label it with "workflow".
|
||||
- If it's about authentication, label it with "auth".
|
||||
- If you can't find an appropriate labels for the issue, follow the previous instruction that starts with "IMPORTANT:".
|
||||
|
||||
## Triaging Workflow
|
||||
|
||||
Each issue will have flags indicating what actions are needed:
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# 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
|
||||
@@ -0,0 +1,93 @@
|
||||
# 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 logging
|
||||
import random
|
||||
|
||||
from google.adk.agents.llm_agent import Agent
|
||||
from google.adk.models import Gemma3Ollama
|
||||
|
||||
litellm_logger = logging.getLogger("LiteLLM")
|
||||
litellm_logger.setLevel(logging.WARNING)
|
||||
|
||||
|
||||
def roll_die(sides: int) -> 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.
|
||||
"""
|
||||
return random.randint(1, sides)
|
||||
|
||||
|
||||
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=Gemma3Ollama(),
|
||||
name="data_processing_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 rolls, 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,
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,77 @@
|
||||
# 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 time
|
||||
|
||||
import agent
|
||||
from dotenv import load_dotenv
|
||||
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
|
||||
from google.adk.cli.utils import logs
|
||||
from google.adk.runners import Runner
|
||||
from google.adk.sessions.in_memory_session_service import InMemorySessionService
|
||||
from google.adk.sessions.session import Session
|
||||
from google.genai import types
|
||||
|
||||
load_dotenv(override=True)
|
||||
logs.log_to_tmp_folder()
|
||||
|
||||
|
||||
async def main():
|
||||
|
||||
app_name = 'my_app'
|
||||
user_id_1 = 'user1'
|
||||
session_service = InMemorySessionService()
|
||||
artifact_service = InMemoryArtifactService()
|
||||
runner = Runner(
|
||||
app_name=app_name,
|
||||
agent=agent.root_agent,
|
||||
artifact_service=artifact_service,
|
||||
session_service=session_service,
|
||||
)
|
||||
session_1 = await session_service.create_session(
|
||||
app_name=app_name, user_id=user_id_1
|
||||
)
|
||||
|
||||
async def run_prompt(session: Session, new_message: str):
|
||||
content = types.Content(
|
||||
role='user', parts=[types.Part.from_text(text=new_message)]
|
||||
)
|
||||
print('** User says:', content.model_dump(exclude_none=True))
|
||||
async for event in runner.run_async(
|
||||
user_id=user_id_1,
|
||||
session_id=session.id,
|
||||
new_message=content,
|
||||
):
|
||||
if event.content.parts and event.content.parts[0].text:
|
||||
print(f'** {event.author}: {event.content.parts[0].text}')
|
||||
|
||||
start_time = time.time()
|
||||
print('Start time:', start_time)
|
||||
print('------------------------------------')
|
||||
await run_prompt(session_1, 'Hi, introduce yourself.')
|
||||
await run_prompt(
|
||||
session_1, 'Roll a die with 100 sides and check if it is prime'
|
||||
)
|
||||
await run_prompt(session_1, 'Roll it again.')
|
||||
await run_prompt(session_1, 'What numbers did I get?')
|
||||
end_time = time.time()
|
||||
print('------------------------------------')
|
||||
print('End time:', end_time)
|
||||
print('Total time:', end_time - start_time)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -18,7 +18,7 @@ This example demonstrates an agent using a long-running tool (`ask_for_approval`
|
||||
# Example: After external approval
|
||||
updated_tool_output_data = {
|
||||
"status": "approved",
|
||||
"ticket-id": ticket_id, # from original call
|
||||
"ticketId": ticket_id, # from original call
|
||||
# ... other relevant updated data
|
||||
}
|
||||
|
||||
@@ -31,12 +31,13 @@ This example demonstrates an agent using a long-running tool (`ask_for_approval`
|
||||
)
|
||||
|
||||
# Send this back to the agent
|
||||
await runner.run_async(
|
||||
async for _ in runner.run_async(
|
||||
# ... session_id, user_id ...
|
||||
new_message=types.Content(
|
||||
parts=[updated_function_response_part], role="user"
|
||||
),
|
||||
)
|
||||
):
|
||||
pass # exhaust generator (or handle events)
|
||||
```
|
||||
6. **Agent Acts on Update**: The agent receives this message containing the `types.FunctionResponse` and, based on its instructions, proceeds with the next steps (e.g., calling another tool like `reimburse`).
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# Interactions API Sample Agent
|
||||
|
||||
This sample agent demonstrates the Interactions API integration in ADK. The
|
||||
Interactions API provides stateful conversation capabilities, allowing chained
|
||||
interactions using `previous_interaction_id` instead of sending full
|
||||
conversation history.
|
||||
|
||||
## Features Tested
|
||||
|
||||
1. **Basic Text Generation** - Simple conversation without tools
|
||||
2. **Google Search Tool** - Web search using `GoogleSearchTool` with
|
||||
`bypass_multi_tools_limit=True`
|
||||
3. **Multi-Turn Conversations** - Stateful interactions with context retention
|
||||
via `previous_interaction_id`
|
||||
4. **Custom Function Tool** - Weather lookup using `get_current_weather`
|
||||
|
||||
## Important: Tool Compatibility
|
||||
|
||||
The Interactions API does **NOT** support mixing custom function calling tools
|
||||
with built-in tools (like `google_search`) in the same agent. To work around
|
||||
this limitation:
|
||||
|
||||
```python
|
||||
# Use bypass_multi_tools_limit=True to convert google_search to a function tool
|
||||
GoogleSearchTool(bypass_multi_tools_limit=True)
|
||||
```
|
||||
|
||||
This converts the built-in `google_search` to a function calling tool (via
|
||||
`GoogleSearchAgentTool`), which allows it to work alongside custom function
|
||||
tools.
|
||||
|
||||
## How to Run
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```bash
|
||||
# From the adk-python root directory
|
||||
uv sync --all-extras
|
||||
source .venv/bin/activate
|
||||
|
||||
# Set up authentication (choose one):
|
||||
# Option 1: Using Google Cloud credentials
|
||||
export GOOGLE_CLOUD_PROJECT=your-project-id
|
||||
|
||||
# Option 2: Using API Key
|
||||
export GOOGLE_API_KEY=your-api-key
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
cd contributing/samples
|
||||
|
||||
# Run automated tests with Interactions API
|
||||
python -m interactions_api.main
|
||||
```
|
||||
|
||||
## Key Differences: Interactions API vs Standard API
|
||||
|
||||
### Interactions API (`use_interactions_api=True`)
|
||||
- Uses stateful interactions via `previous_interaction_id`
|
||||
- Only sends current turn contents when chaining interactions
|
||||
- Returns `interaction_id` in responses for chaining
|
||||
- Ideal for long conversations with many turns
|
||||
- Context caching is not used (state maintained via interaction chaining)
|
||||
|
||||
### Standard API (`use_interactions_api=False`)
|
||||
- Uses stateless `generate_content` calls
|
||||
- Sends full conversation history with each request
|
||||
- No interaction IDs in responses
|
||||
- Context caching can be used
|
||||
|
||||
## Code Structure
|
||||
|
||||
```
|
||||
interactions_api/
|
||||
├── __init__.py # Package initialization
|
||||
├── agent.py # Agent definition with Interactions API
|
||||
├── main.py # Test runner
|
||||
├── test_interactions_curl.sh # cURL-based API tests
|
||||
├── test_interactions_direct.py # Direct API tests
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Agent Configuration
|
||||
|
||||
```python
|
||||
from google.adk.agents.llm_agent import Agent
|
||||
from google.adk.models.google_llm import Gemini
|
||||
from google.adk.tools.google_search_tool import GoogleSearchTool
|
||||
|
||||
root_agent = Agent(
|
||||
model=Gemini(
|
||||
model="gemini-2.5-flash",
|
||||
use_interactions_api=True, # Enable Interactions API
|
||||
),
|
||||
name="interactions_test_agent",
|
||||
tools=[
|
||||
GoogleSearchTool(bypass_multi_tools_limit=True), # Converted to function tool
|
||||
get_current_weather, # Custom function tool
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
============================================================
|
||||
TEST 1: Basic Text Generation
|
||||
============================================================
|
||||
|
||||
>> User: Hello! What can you help me with?
|
||||
<< Agent: Hello! I can help you with: 1) Search the web...
|
||||
[Interaction ID: v1_abc123...]
|
||||
PASSED: Basic text generation works
|
||||
|
||||
============================================================
|
||||
TEST 2: Function Calling (Google Search Tool)
|
||||
============================================================
|
||||
|
||||
>> User: Search for the capital of France.
|
||||
[Tool Call] google_search_agent({'request': 'capital of France'})
|
||||
[Tool Result] google_search_agent: {'result': 'The capital of France is Paris...'}
|
||||
<< Agent: The capital of France is Paris.
|
||||
[Interaction ID: v1_def456...]
|
||||
PASSED: Google search tool works
|
||||
|
||||
============================================================
|
||||
TEST 3: Multi-Turn Conversation (Stateful)
|
||||
============================================================
|
||||
|
||||
>> User: Remember the number 42.
|
||||
<< Agent: I'll remember that number - 42.
|
||||
[Interaction ID: v1_ghi789...]
|
||||
|
||||
>> User: What number did I ask you to remember?
|
||||
<< Agent: You asked me to remember the number 42.
|
||||
[Interaction ID: v1_jkl012...]
|
||||
PASSED: Multi-turn conversation works with context retention
|
||||
|
||||
============================================================
|
||||
TEST 5: Custom Function Tool (get_current_weather)
|
||||
============================================================
|
||||
|
||||
>> User: What's the weather like in Tokyo?
|
||||
[Tool Call] get_current_weather({'city': 'Tokyo'})
|
||||
[Tool Result] get_current_weather: {'city': 'Tokyo', 'temperature_f': 68, ...}
|
||||
<< Agent: The weather in Tokyo is 68F and Partly Cloudy.
|
||||
[Interaction ID: v1_mno345...]
|
||||
PASSED: Custom function tool works with bypass_multi_tools_limit
|
||||
|
||||
ALL TESTS PASSED (Interactions API)
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
# 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.
|
||||
|
||||
"""Sample agent for testing the Interactions API integration."""
|
||||
|
||||
from . import agent
|
||||
@@ -0,0 +1,105 @@
|
||||
# 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.
|
||||
|
||||
"""Agent definition for testing the Interactions API integration.
|
||||
|
||||
NOTE: The Interactions API does NOT support mixing custom function calling tools
|
||||
with built-in tools in the same agent. To work around this limitation, we use
|
||||
bypass_multi_tools_limit=True on GoogleSearchTool, which converts the built-in
|
||||
google_search to a function calling tool (via GoogleSearchAgentTool).
|
||||
|
||||
The bypass is only triggered when len(agent.tools) > 1, so we include multiple
|
||||
tools in the agent (GoogleSearchTool + get_current_weather).
|
||||
|
||||
With bypass_multi_tools_limit=True and multiple tools, all tools become function
|
||||
calling tools, which allows mixing google_search with custom function tools.
|
||||
"""
|
||||
|
||||
from google.adk.agents.llm_agent import Agent
|
||||
from google.adk.models.google_llm import Gemini
|
||||
from google.adk.tools.google_search_tool import GoogleSearchTool
|
||||
|
||||
|
||||
def get_current_weather(city: str) -> dict:
|
||||
"""Get the current weather for a city.
|
||||
|
||||
This is a mock implementation for testing purposes.
|
||||
|
||||
Args:
|
||||
city: The name of the city to get weather for.
|
||||
|
||||
Returns:
|
||||
A dictionary containing weather information.
|
||||
"""
|
||||
# Mock weather data for testing
|
||||
weather_data = {
|
||||
"new york": {"temperature": 72, "condition": "Sunny", "humidity": 45},
|
||||
"london": {"temperature": 59, "condition": "Cloudy", "humidity": 78},
|
||||
"tokyo": {
|
||||
"temperature": 68,
|
||||
"condition": "Partly Cloudy",
|
||||
"humidity": 60,
|
||||
},
|
||||
"paris": {"temperature": 64, "condition": "Rainy", "humidity": 85},
|
||||
"sydney": {"temperature": 77, "condition": "Clear", "humidity": 55},
|
||||
}
|
||||
|
||||
city_lower = city.lower()
|
||||
if city_lower in weather_data:
|
||||
data = weather_data[city_lower]
|
||||
return {
|
||||
"city": city,
|
||||
"temperature_f": data["temperature"],
|
||||
"condition": data["condition"],
|
||||
"humidity": data["humidity"],
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"city": city,
|
||||
"temperature_f": 70,
|
||||
"condition": "Unknown",
|
||||
"humidity": 50,
|
||||
"note": "Weather data not available, using defaults",
|
||||
}
|
||||
|
||||
|
||||
# Main agent with google_search (via bypass) and custom function tools
|
||||
# Using bypass_multi_tools_limit=True converts google_search to a function calling tool.
|
||||
# We need len(tools) > 1 to trigger the bypass, so we include get_current_weather directly.
|
||||
# This allows mixing google_search with custom function tools via the Interactions API.
|
||||
#
|
||||
# NOTE: code_executor is not compatible with function calling mode because the model
|
||||
# tries to call a function (e.g., run_code) instead of outputting code in markdown.
|
||||
root_agent = Agent(
|
||||
model=Gemini(
|
||||
model="gemini-2.5-flash",
|
||||
use_interactions_api=True,
|
||||
),
|
||||
name="interactions_test_agent",
|
||||
description="An agent for testing the Interactions API integration",
|
||||
instruction="""You are a helpful assistant that can:
|
||||
|
||||
1. Search the web for information using google_search
|
||||
2. Get weather information using get_current_weather
|
||||
|
||||
When users ask for information that requires searching, use google_search.
|
||||
When users ask about weather, use get_current_weather.
|
||||
|
||||
Be concise and helpful in your responses. Always confirm what you did.
|
||||
""",
|
||||
tools=[
|
||||
GoogleSearchTool(bypass_multi_tools_limit=True),
|
||||
get_current_weather,
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,420 @@
|
||||
# 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.
|
||||
|
||||
"""Main script for testing the Interactions API integration.
|
||||
|
||||
This script tests the following features:
|
||||
1. Basic text generation
|
||||
2. Google Search tool (via bypass_multi_tools_limit)
|
||||
3. Multi-turn conversations with stateful interactions
|
||||
4. Google Search tool (additional coverage)
|
||||
5. Custom function tool (get_current_weather)
|
||||
|
||||
NOTE: The Interactions API does NOT support mixing custom function calling tools
|
||||
with built-in tools. To work around this, we use bypass_multi_tools_limit=True
|
||||
on GoogleSearchTool, which converts it to a function calling tool (via
|
||||
GoogleSearchAgentTool). The bypass only triggers when len(agent.tools) > 1,
|
||||
so we include both GoogleSearchTool and get_current_weather in the agent.
|
||||
|
||||
NOTE: Code execution via UnsafeLocalCodeExecutor is not compatible with function
|
||||
calling mode because the model tries to call a function instead of outputting
|
||||
code in markdown.
|
||||
|
||||
Run with:
|
||||
cd contributing/samples
|
||||
python -m interactions_api_test.main
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from google.adk.agents.run_config import RunConfig
|
||||
from google.adk.cli.utils import logs
|
||||
from google.adk.runners import InMemoryRunner
|
||||
from google.adk.runners import Runner
|
||||
from google.genai import types
|
||||
|
||||
from .agent import root_agent
|
||||
|
||||
# Load .env from the samples directory (parent of this module's directory)
|
||||
_env_path = Path(__file__).parent.parent / ".env"
|
||||
load_dotenv(_env_path)
|
||||
|
||||
APP_NAME = "interactions_api_test_app"
|
||||
USER_ID = "test_user"
|
||||
|
||||
|
||||
async def call_agent_async(
|
||||
runner: Runner,
|
||||
user_id: str,
|
||||
session_id: str,
|
||||
prompt: str,
|
||||
agent_name: str = "",
|
||||
show_interaction_id: bool = True,
|
||||
) -> tuple[str, Optional[str]]:
|
||||
"""Call the agent asynchronously with the user's prompt.
|
||||
|
||||
Args:
|
||||
runner: The agent runner
|
||||
user_id: The user ID
|
||||
session_id: The session ID
|
||||
prompt: The prompt to send
|
||||
agent_name: The expected agent name for filtering responses
|
||||
show_interaction_id: Whether to show interaction IDs in output
|
||||
|
||||
Returns:
|
||||
A tuple of (response_text, interaction_id)
|
||||
"""
|
||||
content = types.Content(
|
||||
role="user", parts=[types.Part.from_text(text=prompt)]
|
||||
)
|
||||
|
||||
final_response_text = ""
|
||||
last_interaction_id = None
|
||||
|
||||
print(f"\n>> User: {prompt}")
|
||||
|
||||
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),
|
||||
):
|
||||
# Track interaction ID if available
|
||||
if event.interaction_id:
|
||||
last_interaction_id = event.interaction_id
|
||||
|
||||
# Show function calls
|
||||
if event.get_function_calls():
|
||||
for fc in event.get_function_calls():
|
||||
print(f" [Tool Call] {fc.name}({fc.args})")
|
||||
|
||||
# Show function responses
|
||||
if event.get_function_responses():
|
||||
for fr in event.get_function_responses():
|
||||
print(f" [Tool Result] {fr.name}: {fr.response}")
|
||||
|
||||
# Collect text responses from the agent (not user, not partial)
|
||||
if (
|
||||
event.content
|
||||
and event.content.parts
|
||||
and event.author != "user"
|
||||
and not event.partial
|
||||
):
|
||||
for part in event.content.parts:
|
||||
if part.text:
|
||||
# Filter by agent name if provided, otherwise accept any non-user
|
||||
if not agent_name or event.author == agent_name:
|
||||
final_response_text += part.text
|
||||
|
||||
print(f"<< Agent: {final_response_text}")
|
||||
if show_interaction_id and last_interaction_id:
|
||||
print(f" [Interaction ID: {last_interaction_id}]")
|
||||
|
||||
return final_response_text, last_interaction_id
|
||||
|
||||
|
||||
async def test_basic_text_generation(runner: Runner, session_id: str):
|
||||
"""Test basic text generation without tools."""
|
||||
print("\n" + "=" * 60)
|
||||
print("TEST 1: Basic Text Generation")
|
||||
print("=" * 60)
|
||||
|
||||
response, interaction_id = await call_agent_async(
|
||||
runner, USER_ID, session_id, "Hello! What can you help me with?"
|
||||
)
|
||||
|
||||
assert response, "Expected a non-empty response"
|
||||
print("PASSED: Basic text generation works")
|
||||
return interaction_id
|
||||
|
||||
|
||||
async def test_function_calling(runner: Runner, session_id: str):
|
||||
"""Test function calling with the google_search tool."""
|
||||
print("\n" + "=" * 60)
|
||||
print("TEST 2: Function Calling (Google Search Tool)")
|
||||
print("=" * 60)
|
||||
|
||||
response, interaction_id = await call_agent_async(
|
||||
runner,
|
||||
USER_ID,
|
||||
session_id,
|
||||
"Search for the capital of France.",
|
||||
)
|
||||
|
||||
assert response, "Expected a non-empty response"
|
||||
assert "paris" in response.lower(), f"Expected Paris in response: {response}"
|
||||
print("PASSED: Google search tool works")
|
||||
return interaction_id
|
||||
|
||||
|
||||
async def test_multi_turn_conversation(runner: Runner, session_id: str):
|
||||
"""Test multi-turn conversation to verify stateful interactions."""
|
||||
print("\n" + "=" * 60)
|
||||
print("TEST 3: Multi-Turn Conversation (Stateful)")
|
||||
print("=" * 60)
|
||||
|
||||
# Turn 1: Tell the agent a fact directly (test conversation memory)
|
||||
response1, id1 = await call_agent_async(
|
||||
runner,
|
||||
USER_ID,
|
||||
session_id,
|
||||
"My favorite color is blue. Just acknowledge this, don't use any tools.",
|
||||
)
|
||||
assert response1, "Expected a response for turn 1"
|
||||
print(f" Turn 1 interaction_id: {id1}")
|
||||
|
||||
# Turn 2: Ask about something else (use weather tool to add variety)
|
||||
response2, id2 = await call_agent_async(
|
||||
runner,
|
||||
USER_ID,
|
||||
session_id,
|
||||
"What's the weather like in London?",
|
||||
)
|
||||
assert response2, "Expected a response for turn 2"
|
||||
assert (
|
||||
"59" in response2
|
||||
or "london" in response2.lower()
|
||||
or "cloudy" in response2.lower()
|
||||
), f"Expected London weather info in response: {response2}"
|
||||
print(f" Turn 2 interaction_id: {id2}")
|
||||
|
||||
# Turn 3: Ask the agent to recall conversation context
|
||||
response3, id3 = await call_agent_async(
|
||||
runner,
|
||||
USER_ID,
|
||||
session_id,
|
||||
"What is my favorite color that I mentioned earlier in our conversation?",
|
||||
)
|
||||
assert response3, "Expected a response for turn 3"
|
||||
assert (
|
||||
"blue" in response3.lower()
|
||||
), f"Expected agent to remember the color 'blue': {response3}"
|
||||
print(f" Turn 3 interaction_id: {id3}")
|
||||
|
||||
# Verify interaction IDs are different (new interactions) but chained
|
||||
if id1 and id2 and id3:
|
||||
print(f" Interaction chain: {id1} -> {id2} -> {id3}")
|
||||
|
||||
print("PASSED: Multi-turn conversation works with context retention")
|
||||
|
||||
|
||||
async def test_google_search_tool(runner: Runner, session_id: str):
|
||||
"""Test the google_search built-in tool."""
|
||||
print("\n" + "=" * 60)
|
||||
print("TEST 4: Google Search Tool (Additional)")
|
||||
print("=" * 60)
|
||||
|
||||
response, interaction_id = await call_agent_async(
|
||||
runner,
|
||||
USER_ID,
|
||||
session_id,
|
||||
"Use google search to find out who wrote the novel '1984'.",
|
||||
)
|
||||
|
||||
assert response, "Expected a non-empty response"
|
||||
assert (
|
||||
"orwell" in response.lower() or "george" in response.lower()
|
||||
), f"Expected George Orwell in response: {response}"
|
||||
print("PASSED: Google search built-in tool works")
|
||||
|
||||
|
||||
async def test_custom_function_tool(runner: Runner, session_id: str):
|
||||
"""Test the custom function tool alongside google_search.
|
||||
|
||||
The root_agent has both GoogleSearchTool (with bypass_multi_tools_limit=True)
|
||||
and get_current_weather. This tests that function calling tools work with
|
||||
the Interactions API when all tools are function calling types.
|
||||
"""
|
||||
print("\n" + "=" * 60)
|
||||
print("TEST 5: Custom Function Tool (get_current_weather)")
|
||||
print("=" * 60)
|
||||
|
||||
response, interaction_id = await call_agent_async(
|
||||
runner,
|
||||
USER_ID,
|
||||
session_id,
|
||||
"What's the weather like in Tokyo?",
|
||||
)
|
||||
|
||||
assert response, "Expected a non-empty response"
|
||||
# The mock weather data for Tokyo has temperature 68, condition "Partly Cloudy"
|
||||
assert (
|
||||
"68" in response
|
||||
or "partly" in response.lower()
|
||||
or "tokyo" in response.lower()
|
||||
), f"Expected weather info for Tokyo in response: {response}"
|
||||
print("PASSED: Custom function tool works with bypass_multi_tools_limit")
|
||||
return interaction_id
|
||||
|
||||
|
||||
def check_interactions_api_available() -> bool:
|
||||
"""Check if the interactions API is available in the SDK."""
|
||||
try:
|
||||
from google.genai import Client
|
||||
|
||||
client = Client()
|
||||
# Check if interactions attribute exists
|
||||
return hasattr(client.aio, "interactions")
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
async def run_all_tests():
|
||||
"""Run all tests with the Interactions API."""
|
||||
print("\n" + "#" * 70)
|
||||
print("# Running tests with Interactions API")
|
||||
print("#" * 70)
|
||||
|
||||
# Check if interactions API is available
|
||||
if not check_interactions_api_available():
|
||||
print("\nERROR: Interactions API is not available in the current SDK.")
|
||||
print("The interactions API requires a SDK version with this feature.")
|
||||
print("To use the interactions API, ensure you have the SDK with")
|
||||
print("interactions support installed (e.g., from private-python-genai).")
|
||||
return False
|
||||
|
||||
test_agent = root_agent
|
||||
|
||||
runner = InMemoryRunner(
|
||||
agent=test_agent,
|
||||
app_name=APP_NAME,
|
||||
)
|
||||
|
||||
# Create a new session
|
||||
session = await runner.session_service.create_session(
|
||||
user_id=USER_ID,
|
||||
app_name=APP_NAME,
|
||||
)
|
||||
print(f"\nSession created: {session.id}")
|
||||
|
||||
try:
|
||||
# Run all tests
|
||||
await test_basic_text_generation(runner, session.id)
|
||||
await test_function_calling(runner, session.id)
|
||||
await test_multi_turn_conversation(runner, session.id)
|
||||
await test_google_search_tool(runner, session.id)
|
||||
await test_custom_function_tool(runner, session.id)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ALL TESTS PASSED (Interactions API)")
|
||||
print("=" * 60)
|
||||
return True
|
||||
|
||||
except AssertionError as e:
|
||||
print(f"\nTEST FAILED: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"\nERROR: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
async def interactive_mode():
|
||||
"""Run in interactive mode for manual testing."""
|
||||
# Check if interactions API is available
|
||||
if not check_interactions_api_available():
|
||||
print("\nERROR: Interactions API is not available in the current SDK.")
|
||||
print("To use the interactions API, ensure you have the SDK with")
|
||||
print("interactions support installed (e.g., from private-python-genai).")
|
||||
return
|
||||
|
||||
print("\nInteractive mode with Interactions API")
|
||||
print("Type 'quit' to exit, 'new' for a new session\n")
|
||||
|
||||
test_agent = agent.root_agent
|
||||
|
||||
runner = InMemoryRunner(
|
||||
agent=test_agent,
|
||||
app_name=APP_NAME,
|
||||
)
|
||||
|
||||
session = await runner.session_service.create_session(
|
||||
user_id=USER_ID,
|
||||
app_name=APP_NAME,
|
||||
)
|
||||
print(f"Session created: {session.id}\n")
|
||||
|
||||
while True:
|
||||
try:
|
||||
user_input = input("You: ").strip()
|
||||
if not user_input:
|
||||
continue
|
||||
if user_input.lower() == "quit":
|
||||
break
|
||||
if user_input.lower() == "new":
|
||||
session = await runner.session_service.create_session(
|
||||
user_id=USER_ID,
|
||||
app_name=APP_NAME,
|
||||
)
|
||||
print(f"New session created: {session.id}\n")
|
||||
continue
|
||||
|
||||
await call_agent_async(runner, USER_ID, session.id, user_input)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
print("\nGoodbye!")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Test the Interactions API integration"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
choices=["test", "interactive"],
|
||||
default="test",
|
||||
help=(
|
||||
"Run mode: 'test' runs automated tests, 'interactive' for manual"
|
||||
" testing"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug",
|
||||
action="store_true",
|
||||
help="Enable debug logging",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.debug:
|
||||
logs.setup_adk_logger(level=logging.DEBUG)
|
||||
else:
|
||||
logs.setup_adk_logger(level=logging.INFO)
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
if args.mode == "test":
|
||||
success = asyncio.run(run_all_tests())
|
||||
if not success:
|
||||
exit(1)
|
||||
|
||||
elif args.mode == "interactive":
|
||||
asyncio.run(interactive_mode())
|
||||
|
||||
end_time = time.time()
|
||||
print(f"\nTotal execution time: {end_time - start_time:.2f} seconds")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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
|
||||
@@ -0,0 +1,39 @@
|
||||
# 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 __future__ import annotations
|
||||
|
||||
from google.adk.agents.llm_agent import LlmAgent
|
||||
from google.adk.agents.sequential_agent import SequentialAgent
|
||||
from google.adk.models.lite_llm import LiteLlm
|
||||
|
||||
ollama_model = LiteLlm(model="ollama_chat/qwen2.5:7b")
|
||||
|
||||
hello_agent = LlmAgent(
|
||||
name="hello_step",
|
||||
instruction="Say hello to the user. Be concise.",
|
||||
model=ollama_model,
|
||||
)
|
||||
|
||||
summarize_agent = LlmAgent(
|
||||
name="summarize_step",
|
||||
instruction="Summarize the previous assistant message in 5 words.",
|
||||
model=ollama_model,
|
||||
)
|
||||
|
||||
root_agent = SequentialAgent(
|
||||
name="ollama_seq_test",
|
||||
description="Two-step sanity check for Ollama LiteLLM chat.",
|
||||
sub_agents=[hello_agent, summarize_agent],
|
||||
)
|
||||
@@ -32,7 +32,7 @@ logs.log_to_tmp_folder()
|
||||
async def main():
|
||||
app_name = 'migrate_session_db_app'
|
||||
user_id_1 = 'user1'
|
||||
session_service = DatabaseSessionService('sqlite:///./sessions.db')
|
||||
session_service = DatabaseSessionService('sqlite+aiosqlite:///./sessions.db')
|
||||
artifact_service = InMemoryArtifactService()
|
||||
runner = Runner(
|
||||
app_name=app_name,
|
||||
|
||||
@@ -18,6 +18,7 @@ from google.adk.agents.llm_agent import LlmAgent
|
||||
from google.adk.auth.auth_credential import AuthCredentialTypes
|
||||
from google.adk.tools.google_tool import GoogleTool
|
||||
from google.adk.tools.spanner.settings import Capabilities
|
||||
from google.adk.tools.spanner.settings import QueryResultMode
|
||||
from google.adk.tools.spanner.settings import SpannerToolSettings
|
||||
from google.adk.tools.spanner.spanner_credentials import SpannerCredentialsConfig
|
||||
from google.adk.tools.spanner.spanner_toolset import SpannerToolset
|
||||
@@ -34,7 +35,10 @@ CREDENTIALS_TYPE = None
|
||||
|
||||
|
||||
# Define Spanner tool config with read capability set to allowed.
|
||||
tool_settings = SpannerToolSettings(capabilities=[Capabilities.DATA_READ])
|
||||
tool_settings = SpannerToolSettings(
|
||||
capabilities=[Capabilities.DATA_READ],
|
||||
query_result_mode=QueryResultMode.DICT_LIST,
|
||||
)
|
||||
|
||||
if CREDENTIALS_TYPE == AuthCredentialTypes.OAUTH2:
|
||||
# Initialize the tools to do interactive OAuth
|
||||
|
||||
+5
-5
@@ -30,7 +30,7 @@ dependencies = [
|
||||
"anyio>=4.9.0, <5.0.0", # For MCP Session Manager
|
||||
"authlib>=1.5.1, <2.0.0", # For RestAPI Tool
|
||||
"click>=8.1.8, <9.0.0", # For CLI tools
|
||||
"fastapi>=0.115.0, <0.119.0", # FastAPI framework
|
||||
"fastapi>=0.115.0, <0.124.0", # FastAPI framework
|
||||
"google-api-python-client>=2.157.0, <3.0.0", # Google API client discovery
|
||||
"google-cloud-aiplatform[agent_engines]>=1.125.0, <2.0.0", # For VertexAI integrations, e.g. example store.
|
||||
"google-cloud-bigquery-storage>=2.0.0",
|
||||
@@ -41,7 +41,7 @@ dependencies = [
|
||||
"google-cloud-spanner>=3.56.0, <4.0.0", # For Spanner database
|
||||
"google-cloud-speech>=2.30.0, <3.0.0", # For Audio Transcription
|
||||
"google-cloud-storage>=2.18.0, <4.0.0", # For GCS Artifact service
|
||||
"google-genai>=1.51.0, <2.0.0", # Google GenAI SDK
|
||||
"google-genai>=1.55.0, <2.0.0", # Google GenAI SDK
|
||||
"graphviz>=0.20.2, <1.0.0", # Graphviz for graph rendering
|
||||
"jsonschema>=4.23.0, <5.0.0", # Agent Builder config validation
|
||||
"mcp>=1.10.0, <2.0.0", # For MCP Toolset
|
||||
@@ -59,7 +59,7 @@ dependencies = [
|
||||
"requests>=2.32.4, <3.0.0",
|
||||
"sqlalchemy-spanner>=1.14.0", # Spanner database session service
|
||||
"sqlalchemy>=2.0, <3.0.0", # SQL database ORM
|
||||
"starlette>=0.46.2, <1.0.0", # For FastAPI CLI
|
||||
"starlette>=0.49.1, <1.0.0", # For FastAPI CLI
|
||||
"tenacity>=9.0.0, <10.0.0", # For Retry management
|
||||
"typing-extensions>=4.5, <5",
|
||||
"tzlocal>=5.3, <6.0", # Time zone utilities
|
||||
@@ -116,7 +116,7 @@ test = [
|
||||
# go/keep-sorted start
|
||||
"a2a-sdk>=0.3.0,<0.4.0",
|
||||
"anthropic>=0.43.0", # For anthropic model tests
|
||||
"crewai[tools];python_version>='3.10' and python_version<'3.12'", # For CrewaiTool tests; chromadb/pypika fail on 3.12+
|
||||
"crewai[tools];python_version>='3.11' and python_version<'3.12'", # For CrewaiTool tests; chromadb/pypika fail on 3.12+
|
||||
"kubernetes>=29.0.0", # For GkeCodeExecutor
|
||||
"langchain-community>=0.3.17",
|
||||
"langgraph>=0.2.60, <0.4.8", # For LangGraphAgent
|
||||
@@ -146,7 +146,7 @@ docs = [
|
||||
extensions = [
|
||||
"anthropic>=0.43.0", # For anthropic model support
|
||||
"beautifulsoup4>=3.2.2", # For load_web_page tool.
|
||||
"crewai[tools];python_version>='3.10' and python_version<'3.12'", # For CrewaiTool; chromadb/pypika fail on 3.12+
|
||||
"crewai[tools];python_version>='3.11' and python_version<'3.12'", # For CrewaiTool; chromadb/pypika fail on 3.12+
|
||||
"docker>=7.0.0", # For ContainerCodeExecutor
|
||||
"kubernetes>=29.0.0", # For GkeCodeExecutor
|
||||
"langgraph>=0.2.60, <0.4.8", # For LangGraphAgent
|
||||
|
||||
@@ -140,6 +140,7 @@ def _get_context_metadata(
|
||||
("custom_metadata", event.custom_metadata),
|
||||
("usage_metadata", event.usage_metadata),
|
||||
("error_code", event.error_code),
|
||||
("actions", event.actions),
|
||||
]
|
||||
|
||||
for field_name, field_value in optional_fields:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user