mirror of
https://github.com/EvolutionAPI/adk-python.git
synced 2025-12-18 11:22:22 -06:00
No public description
PiperOrigin-RevId: 748777998
This commit is contained in:
committed by
hangfei
parent
290058eb05
commit
61d4be2d76
14
tests/integration/fixture/__init__.py
Normal file
14
tests/integration/fixture/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
15
tests/integration/fixture/agent_with_config/__init__.py
Normal file
15
tests/integration/fixture/agent_with_config/__init__.py
Normal file
@@ -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
|
||||
88
tests/integration/fixture/agent_with_config/agent.py
Normal file
88
tests/integration/fixture/agent_with_config/agent.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# 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 google.adk import Agent
|
||||
from google.genai import types
|
||||
|
||||
new_message = types.Content(
|
||||
role="user",
|
||||
parts=[types.Part.from_text(text="Count a number")],
|
||||
)
|
||||
|
||||
google_agent_1 = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="agent_1",
|
||||
description="The first agent in the team.",
|
||||
instruction="Just say 1",
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
temperature=0.1,
|
||||
),
|
||||
)
|
||||
|
||||
google_agent_2 = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="agent_2",
|
||||
description="The second agent in the team.",
|
||||
instruction="Just say 2",
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
temperature=0.2,
|
||||
safety_settings=[{
|
||||
"category": "HARM_CATEGORY_HATE_SPEECH",
|
||||
"threshold": "BLOCK_ONLY_HIGH",
|
||||
}],
|
||||
),
|
||||
)
|
||||
|
||||
google_agent_3 = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="agent_3",
|
||||
description="The third agent in the team.",
|
||||
instruction="Just say 3",
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
temperature=0.5,
|
||||
safety_settings=[{
|
||||
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
|
||||
"threshold": "BLOCK_NONE",
|
||||
}],
|
||||
),
|
||||
)
|
||||
|
||||
google_agent_with_instruction_in_config = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="agent",
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
temperature=0.5, system_instruction="Count 1"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def function():
|
||||
pass
|
||||
|
||||
|
||||
google_agent_with_tools_in_config = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="agent",
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
temperature=0.5, tools=[function]
|
||||
),
|
||||
)
|
||||
|
||||
google_agent_with_response_schema_in_config = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="agent",
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
temperature=0.5, response_schema={"key": "value"}
|
||||
),
|
||||
)
|
||||
15
tests/integration/fixture/callback_agent/__init__.py
Normal file
15
tests/integration/fixture/callback_agent/__init__.py
Normal file
@@ -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
|
||||
105
tests/integration/fixture/callback_agent/agent.py
Normal file
105
tests/integration/fixture/callback_agent/agent.py
Normal file
@@ -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.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from google.adk import Agent
|
||||
from google.adk.agents.callback_context import CallbackContext
|
||||
from google.adk.agents.invocation_context import InvocationContext
|
||||
from google.adk.models import LlmRequest
|
||||
from google.adk.models import LlmResponse
|
||||
from google.genai import types
|
||||
|
||||
|
||||
def before_agent_call_end_invocation(
|
||||
callback_context: CallbackContext,
|
||||
) -> types.Content:
|
||||
return types.Content(
|
||||
role='model',
|
||||
parts=[types.Part(text='End invocation event before agent call.')],
|
||||
)
|
||||
|
||||
|
||||
def before_agent_call(
|
||||
invocation_context: InvocationContext,
|
||||
) -> types.Content:
|
||||
return types.Content(
|
||||
role='model',
|
||||
parts=[types.Part.from_text(text='Plain text event before agent call.')],
|
||||
)
|
||||
|
||||
|
||||
def before_model_call_end_invocation(
|
||||
callback_context: CallbackContext, llm_request: LlmRequest
|
||||
) -> LlmResponse:
|
||||
return LlmResponse(
|
||||
content=types.Content(
|
||||
role='model',
|
||||
parts=[
|
||||
types.Part.from_text(
|
||||
text='End invocation event before model call.'
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def before_model_call(
|
||||
invocation_context: InvocationContext, request: LlmRequest
|
||||
) -> LlmResponse:
|
||||
request.config.system_instruction = 'Just return 999 as response.'
|
||||
return LlmResponse(
|
||||
content=types.Content(
|
||||
role='model',
|
||||
parts=[
|
||||
types.Part.from_text(
|
||||
text='Update request event before model call.'
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def after_model_call(
|
||||
callback_context: CallbackContext,
|
||||
llm_response: LlmResponse,
|
||||
) -> Optional[LlmResponse]:
|
||||
content = llm_response.content
|
||||
if not content or not content.parts or not content.parts[0].text:
|
||||
return
|
||||
|
||||
content.parts[0].text += 'Update response event after model call.'
|
||||
return llm_response
|
||||
|
||||
|
||||
before_agent_callback_agent = Agent(
|
||||
model='gemini-1.5-flash',
|
||||
name='before_agent_callback_agent',
|
||||
instruction='echo 1',
|
||||
before_agent_callback=before_agent_call_end_invocation,
|
||||
)
|
||||
|
||||
before_model_callback_agent = Agent(
|
||||
model='gemini-1.5-flash',
|
||||
name='before_model_callback_agent',
|
||||
instruction='echo 2',
|
||||
before_model_callback=before_model_call_end_invocation,
|
||||
)
|
||||
|
||||
after_model_callback_agent = Agent(
|
||||
model='gemini-1.5-flash',
|
||||
name='after_model_callback_agent',
|
||||
instruction='Say hello',
|
||||
after_model_callback=after_model_call,
|
||||
)
|
||||
1
tests/integration/fixture/context_update_test/OWNERS
Normal file
1
tests/integration/fixture/context_update_test/OWNERS
Normal file
@@ -0,0 +1 @@
|
||||
gkcng
|
||||
15
tests/integration/fixture/context_update_test/__init__.py
Normal file
15
tests/integration/fixture/context_update_test/__init__.py
Normal file
@@ -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
|
||||
43
tests/integration/fixture/context_update_test/agent.py
Normal file
43
tests/integration/fixture/context_update_test/agent.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# 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 typing import List
|
||||
from typing import Union
|
||||
|
||||
from google.adk import Agent
|
||||
from google.adk.tools import ToolContext
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
def update_fc(
|
||||
data_one: str,
|
||||
data_two: Union[int, float, str],
|
||||
data_three: list[str],
|
||||
data_four: List[Union[int, float, str]],
|
||||
tool_context: ToolContext,
|
||||
):
|
||||
"""Simply ask to update these variables in the context"""
|
||||
tool_context.actions.update_state("data_one", data_one)
|
||||
tool_context.actions.update_state("data_two", data_two)
|
||||
tool_context.actions.update_state("data_three", data_three)
|
||||
tool_context.actions.update_state("data_four", data_four)
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="root_agent",
|
||||
instruction="Call tools",
|
||||
flow="auto",
|
||||
tools=[update_fc],
|
||||
)
|
||||
@@ -0,0 +1,582 @@
|
||||
{
|
||||
"id": "ead43200-b575-4241-9248-233b4be4f29a",
|
||||
"context": {
|
||||
"_time": "2024-12-01 09:02:43.531503",
|
||||
"data_one": "RRRR",
|
||||
"data_two": "3.141529",
|
||||
"data_three": [
|
||||
"apple",
|
||||
"banana"
|
||||
],
|
||||
"data_four": [
|
||||
"1",
|
||||
"hello",
|
||||
"3.14"
|
||||
]
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"invocation_id": "6BGrtKJu",
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "hi"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"options": {},
|
||||
"id": "ltzQTqR4",
|
||||
"timestamp": 1733043686.8428597
|
||||
},
|
||||
{
|
||||
"invocation_id": "6BGrtKJu",
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello! 👋 How can I help you today? \n"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"options": {
|
||||
"partial": false
|
||||
},
|
||||
"id": "ClSROx8b",
|
||||
"timestamp": 1733043688.1030986
|
||||
},
|
||||
{
|
||||
"invocation_id": "M3dUcVa8",
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "update data_one to be RRRR, data_two to be 3.141529, data_three to be apple and banana, data_four to be 1, hello, and 3.14"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"options": {},
|
||||
"id": "yxigGwIZ",
|
||||
"timestamp": 1733043745.9900541
|
||||
},
|
||||
{
|
||||
"invocation_id": "M3dUcVa8",
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"function_call": {
|
||||
"args": {
|
||||
"data_four": [
|
||||
"1",
|
||||
"hello",
|
||||
"3.14"
|
||||
],
|
||||
"data_two": "3.141529",
|
||||
"data_three": [
|
||||
"apple",
|
||||
"banana"
|
||||
],
|
||||
"data_one": "RRRR"
|
||||
},
|
||||
"name": "update_fc"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"options": {
|
||||
"partial": false
|
||||
},
|
||||
"id": "8V6de8th",
|
||||
"timestamp": 1733043747.4545543
|
||||
},
|
||||
{
|
||||
"invocation_id": "M3dUcVa8",
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"function_response": {
|
||||
"name": "update_fc",
|
||||
"response": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"options": {
|
||||
"update_context": {
|
||||
"data_one": "RRRR",
|
||||
"data_two": "3.141529",
|
||||
"data_three": [
|
||||
"apple",
|
||||
"banana"
|
||||
],
|
||||
"data_four": [
|
||||
"1",
|
||||
"hello",
|
||||
"3.14"
|
||||
]
|
||||
},
|
||||
"function_call_event_id": "8V6de8th"
|
||||
},
|
||||
"id": "dkTj5v8B",
|
||||
"timestamp": 1733043747.457031
|
||||
},
|
||||
{
|
||||
"invocation_id": "M3dUcVa8",
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "OK. I've updated the data. Anything else? \n"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"options": {
|
||||
"partial": false
|
||||
},
|
||||
"id": "OZ77XR41",
|
||||
"timestamp": 1733043748.7901294
|
||||
}
|
||||
],
|
||||
"past_events": [],
|
||||
"pending_events": {},
|
||||
"artifacts": {},
|
||||
"event_logs": [
|
||||
{
|
||||
"invocation_id": "6BGrtKJu",
|
||||
"event_id": "ClSROx8b",
|
||||
"model_request": {
|
||||
"model": "gemini-1.5-flash",
|
||||
"contents": [
|
||||
{
|
||||
"parts": [
|
||||
{
|
||||
"text": "hi"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"system_instruction": "You are an agent. Your name is root_agent.\nCall tools",
|
||||
"tools": [
|
||||
{
|
||||
"function_declarations": [
|
||||
{
|
||||
"description": "Hello",
|
||||
"name": "update_fc",
|
||||
"parameters": {
|
||||
"type": "OBJECT",
|
||||
"properties": {
|
||||
"data_one": {
|
||||
"type": "STRING"
|
||||
},
|
||||
"data_two": {
|
||||
"type": "STRING"
|
||||
},
|
||||
"data_three": {
|
||||
"type": "ARRAY",
|
||||
"items": {
|
||||
"type": "STRING"
|
||||
}
|
||||
},
|
||||
"data_four": {
|
||||
"type": "ARRAY",
|
||||
"items": {
|
||||
"any_of": [
|
||||
{
|
||||
"type": "INTEGER"
|
||||
},
|
||||
{
|
||||
"type": "NUMBER"
|
||||
},
|
||||
{
|
||||
"type": "STRING"
|
||||
}
|
||||
],
|
||||
"type": "STRING"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"model_response": {
|
||||
"candidates": [
|
||||
{
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello! 👋 How can I help you today? \n"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"avg_logprobs": -0.15831730915949896,
|
||||
"finish_reason": "STOP",
|
||||
"safety_ratings": [
|
||||
{
|
||||
"category": "HARM_CATEGORY_HATE_SPEECH",
|
||||
"probability": "NEGLIGIBLE",
|
||||
"probability_score": 0.071777344,
|
||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
||||
"severity_score": 0.07080078
|
||||
},
|
||||
{
|
||||
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
|
||||
"probability": "NEGLIGIBLE",
|
||||
"probability_score": 0.16308594,
|
||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
||||
"severity_score": 0.14160156
|
||||
},
|
||||
{
|
||||
"category": "HARM_CATEGORY_HARASSMENT",
|
||||
"probability": "NEGLIGIBLE",
|
||||
"probability_score": 0.09423828,
|
||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
||||
"severity_score": 0.037841797
|
||||
},
|
||||
{
|
||||
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
||||
"probability": "NEGLIGIBLE",
|
||||
"probability_score": 0.059326172,
|
||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
||||
"severity_score": 0.02368164
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"model_version": "gemini-1.5-flash-001",
|
||||
"usage_metadata": {
|
||||
"candidates_token_count": 13,
|
||||
"prompt_token_count": 32,
|
||||
"total_token_count": 45
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"invocation_id": "M3dUcVa8",
|
||||
"event_id": "8V6de8th",
|
||||
"model_request": {
|
||||
"model": "gemini-1.5-flash",
|
||||
"contents": [
|
||||
{
|
||||
"parts": [
|
||||
{
|
||||
"text": "hi"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
{
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello! 👋 How can I help you today? \n"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
{
|
||||
"parts": [
|
||||
{
|
||||
"text": "update data_one to be RRRR, data_two to be 3.141529, data_three to be apple and banana, data_four to be 1, hello, and 3.14"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"system_instruction": "You are an agent. Your name is root_agent.\nCall tools",
|
||||
"tools": [
|
||||
{
|
||||
"function_declarations": [
|
||||
{
|
||||
"description": "Hello",
|
||||
"name": "update_fc",
|
||||
"parameters": {
|
||||
"type": "OBJECT",
|
||||
"properties": {
|
||||
"data_one": {
|
||||
"type": "STRING"
|
||||
},
|
||||
"data_two": {
|
||||
"type": "STRING"
|
||||
},
|
||||
"data_three": {
|
||||
"type": "ARRAY",
|
||||
"items": {
|
||||
"type": "STRING"
|
||||
}
|
||||
},
|
||||
"data_four": {
|
||||
"type": "ARRAY",
|
||||
"items": {
|
||||
"any_of": [
|
||||
{
|
||||
"type": "INTEGER"
|
||||
},
|
||||
{
|
||||
"type": "NUMBER"
|
||||
},
|
||||
{
|
||||
"type": "STRING"
|
||||
}
|
||||
],
|
||||
"type": "STRING"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"model_response": {
|
||||
"candidates": [
|
||||
{
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"function_call": {
|
||||
"args": {
|
||||
"data_four": [
|
||||
"1",
|
||||
"hello",
|
||||
"3.14"
|
||||
],
|
||||
"data_two": "3.141529",
|
||||
"data_three": [
|
||||
"apple",
|
||||
"banana"
|
||||
],
|
||||
"data_one": "RRRR"
|
||||
},
|
||||
"name": "update_fc"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"avg_logprobs": -2.100960955431219e-6,
|
||||
"finish_reason": "STOP",
|
||||
"safety_ratings": [
|
||||
{
|
||||
"category": "HARM_CATEGORY_HATE_SPEECH",
|
||||
"probability": "NEGLIGIBLE",
|
||||
"probability_score": 0.12158203,
|
||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
||||
"severity_score": 0.13671875
|
||||
},
|
||||
{
|
||||
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
|
||||
"probability": "NEGLIGIBLE",
|
||||
"probability_score": 0.421875,
|
||||
"severity": "HARM_SEVERITY_LOW",
|
||||
"severity_score": 0.24511719
|
||||
},
|
||||
{
|
||||
"category": "HARM_CATEGORY_HARASSMENT",
|
||||
"probability": "NEGLIGIBLE",
|
||||
"probability_score": 0.15722656,
|
||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
||||
"severity_score": 0.072753906
|
||||
},
|
||||
{
|
||||
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
||||
"probability": "NEGLIGIBLE",
|
||||
"probability_score": 0.083984375,
|
||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
||||
"severity_score": 0.03564453
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"model_version": "gemini-1.5-flash-001",
|
||||
"usage_metadata": {
|
||||
"candidates_token_count": 32,
|
||||
"prompt_token_count": 94,
|
||||
"total_token_count": 126
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"invocation_id": "M3dUcVa8",
|
||||
"event_id": "OZ77XR41",
|
||||
"model_request": {
|
||||
"model": "gemini-1.5-flash",
|
||||
"contents": [
|
||||
{
|
||||
"parts": [
|
||||
{
|
||||
"text": "hi"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
{
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello! 👋 How can I help you today? \n"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
{
|
||||
"parts": [
|
||||
{
|
||||
"text": "update data_one to be RRRR, data_two to be 3.141529, data_three to be apple and banana, data_four to be 1, hello, and 3.14"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
{
|
||||
"parts": [
|
||||
{
|
||||
"function_call": {
|
||||
"args": {
|
||||
"data_four": [
|
||||
"1",
|
||||
"hello",
|
||||
"3.14"
|
||||
],
|
||||
"data_two": "3.141529",
|
||||
"data_three": [
|
||||
"apple",
|
||||
"banana"
|
||||
],
|
||||
"data_one": "RRRR"
|
||||
},
|
||||
"name": "update_fc"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
{
|
||||
"parts": [
|
||||
{
|
||||
"function_response": {
|
||||
"name": "update_fc",
|
||||
"response": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"system_instruction": "You are an agent. Your name is root_agent.\nCall tools",
|
||||
"tools": [
|
||||
{
|
||||
"function_declarations": [
|
||||
{
|
||||
"description": "Hello",
|
||||
"name": "update_fc",
|
||||
"parameters": {
|
||||
"type": "OBJECT",
|
||||
"properties": {
|
||||
"data_one": {
|
||||
"type": "STRING"
|
||||
},
|
||||
"data_two": {
|
||||
"type": "STRING"
|
||||
},
|
||||
"data_three": {
|
||||
"type": "ARRAY",
|
||||
"items": {
|
||||
"type": "STRING"
|
||||
}
|
||||
},
|
||||
"data_four": {
|
||||
"type": "ARRAY",
|
||||
"items": {
|
||||
"any_of": [
|
||||
{
|
||||
"type": "INTEGER"
|
||||
},
|
||||
{
|
||||
"type": "NUMBER"
|
||||
},
|
||||
{
|
||||
"type": "STRING"
|
||||
}
|
||||
],
|
||||
"type": "STRING"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"model_response": {
|
||||
"candidates": [
|
||||
{
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "OK. I've updated the data. Anything else? \n"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"avg_logprobs": -0.22089435373033797,
|
||||
"finish_reason": "STOP",
|
||||
"safety_ratings": [
|
||||
{
|
||||
"category": "HARM_CATEGORY_HATE_SPEECH",
|
||||
"probability": "NEGLIGIBLE",
|
||||
"probability_score": 0.04663086,
|
||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
||||
"severity_score": 0.09423828
|
||||
},
|
||||
{
|
||||
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
|
||||
"probability": "NEGLIGIBLE",
|
||||
"probability_score": 0.18554688,
|
||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
||||
"severity_score": 0.111328125
|
||||
},
|
||||
{
|
||||
"category": "HARM_CATEGORY_HARASSMENT",
|
||||
"probability": "NEGLIGIBLE",
|
||||
"probability_score": 0.071777344,
|
||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
||||
"severity_score": 0.03112793
|
||||
},
|
||||
{
|
||||
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
||||
"probability": "NEGLIGIBLE",
|
||||
"probability_score": 0.043945313,
|
||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
||||
"severity_score": 0.057373047
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"model_version": "gemini-1.5-flash-001",
|
||||
"usage_metadata": {
|
||||
"candidates_token_count": 14,
|
||||
"prompt_token_count": 129,
|
||||
"total_token_count": 143
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
15
tests/integration/fixture/context_variable_agent/__init__.py
Normal file
15
tests/integration/fixture/context_variable_agent/__init__.py
Normal file
@@ -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
|
||||
115
tests/integration/fixture/context_variable_agent/agent.py
Normal file
115
tests/integration/fixture/context_variable_agent/agent.py
Normal file
@@ -0,0 +1,115 @@
|
||||
# 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 typing import List
|
||||
from typing import Union
|
||||
|
||||
from google.adk import Agent
|
||||
from google.adk.agents.invocation_context import InvocationContext
|
||||
from google.adk.planners import PlanReActPlanner
|
||||
from google.adk.tools import ToolContext
|
||||
|
||||
|
||||
def update_fc(
|
||||
data_one: str,
|
||||
data_two: Union[int, float, str],
|
||||
data_three: list[str],
|
||||
data_four: List[Union[int, float, str]],
|
||||
tool_context: ToolContext,
|
||||
) -> str:
|
||||
"""Simply ask to update these variables in the context"""
|
||||
tool_context.actions.update_state('data_one', data_one)
|
||||
tool_context.actions.update_state('data_two', data_two)
|
||||
tool_context.actions.update_state('data_three', data_three)
|
||||
tool_context.actions.update_state('data_four', data_four)
|
||||
return 'The function `update_fc` executed successfully'
|
||||
|
||||
|
||||
def echo_info(customer_id: str) -> str:
|
||||
"""Echo the context variable"""
|
||||
return customer_id
|
||||
|
||||
|
||||
def build_global_instruction(invocation_context: InvocationContext) -> str:
|
||||
return (
|
||||
'This is the gloabl agent instruction for invocation:'
|
||||
f' {invocation_context.invocation_id}.'
|
||||
)
|
||||
|
||||
|
||||
def build_sub_agent_instruction(invocation_context: InvocationContext) -> str:
|
||||
return 'This is the plain text sub agent instruction.'
|
||||
|
||||
|
||||
context_variable_echo_agent = Agent(
|
||||
model='gemini-1.5-flash',
|
||||
name='context_variable_echo_agent',
|
||||
instruction=(
|
||||
'Use the echo_info tool to echo {customerId}, {customerInt},'
|
||||
' {customerFloat}, and {customerJson}. Ask for it if you need to.'
|
||||
),
|
||||
flow='auto',
|
||||
tools=[echo_info],
|
||||
)
|
||||
|
||||
context_variable_with_complicated_format_agent = Agent(
|
||||
model='gemini-1.5-flash',
|
||||
name='context_variable_echo_agent',
|
||||
instruction=(
|
||||
'Use the echo_info tool to echo { customerId }, {{customer_int }, { '
|
||||
" non-identifier-float}}, {artifact.fileName}, {'key1': 'value1'} and"
|
||||
" {{'key2': 'value2'}}. Ask for it if you need to."
|
||||
),
|
||||
flow='auto',
|
||||
tools=[echo_info],
|
||||
)
|
||||
|
||||
context_variable_with_nl_planner_agent = Agent(
|
||||
model='gemini-1.5-flash',
|
||||
name='context_variable_with_nl_planner_agent',
|
||||
instruction=(
|
||||
'Use the echo_info tool to echo {customerId}. Ask for it if you'
|
||||
' need to.'
|
||||
),
|
||||
flow='auto',
|
||||
planner=PlanReActPlanner(),
|
||||
tools=[echo_info],
|
||||
)
|
||||
|
||||
context_variable_with_function_instruction_agent = Agent(
|
||||
model='gemini-1.5-flash',
|
||||
name='context_variable_with_function_instruction_agent',
|
||||
instruction=build_sub_agent_instruction,
|
||||
flow='auto',
|
||||
)
|
||||
|
||||
context_variable_update_agent = Agent(
|
||||
model='gemini-1.5-flash',
|
||||
name='context_variable_update_agent',
|
||||
instruction='Call tools',
|
||||
flow='auto',
|
||||
tools=[update_fc],
|
||||
)
|
||||
|
||||
root_agent = Agent(
|
||||
model='gemini-1.5-flash',
|
||||
name='root_agent',
|
||||
description='The root agent.',
|
||||
flow='auto',
|
||||
global_instruction=build_global_instruction,
|
||||
sub_agents=[
|
||||
context_variable_with_nl_planner_agent,
|
||||
context_variable_update_agent,
|
||||
],
|
||||
)
|
||||
15
tests/integration/fixture/customer_support_ma/__init__.py
Normal file
15
tests/integration/fixture/customer_support_ma/__init__.py
Normal file
@@ -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
|
||||
172
tests/integration/fixture/customer_support_ma/agent.py
Normal file
172
tests/integration/fixture/customer_support_ma/agent.py
Normal file
@@ -0,0 +1,172 @@
|
||||
# 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 sys
|
||||
|
||||
from google.adk import Agent
|
||||
from google.adk.agents import RemoteAgent
|
||||
from google.adk.examples import Example
|
||||
from google.adk.sessions import Session
|
||||
from google.genai import types
|
||||
|
||||
|
||||
def reset_data():
|
||||
pass
|
||||
|
||||
|
||||
def fetch_user_flight_information(customer_email: str) -> str:
|
||||
"""Fetch user flight information."""
|
||||
return """
|
||||
[{"ticket_no": "7240005432906569", "book_ref": "C46E9F", "flight_id": 19250, "flight_no": "LX0112", "departure_airport": "CDG", "arrival_airport": "BSL", "scheduled_departure": "2024-12-30 12:09:03.561731-04:00", "scheduled_arrival": "2024-12-30 13:39:03.561731-04:00", "seat_no": "18E", "fare_conditions": "Economy"}]
|
||||
"""
|
||||
|
||||
|
||||
def list_customer_flights(customer_email: str) -> str:
|
||||
return "{'flights': [{'book_ref': 'C46E9F'}]}"
|
||||
|
||||
|
||||
def update_ticket_to_new_flight(ticket_no: str, new_flight_id: str) -> str:
|
||||
return 'OK, your ticket has been updated.'
|
||||
|
||||
|
||||
def lookup_company_policy(topic: str) -> str:
|
||||
"""Lookup policies for flight cancelation and rebooking."""
|
||||
return """
|
||||
1. How can I change my booking?
|
||||
* The ticket number must start with 724 (SWISS ticket no./plate).
|
||||
* The ticket was not paid for by barter or voucher (there are exceptions to voucher payments; if the ticket was paid for in full by voucher, then it may be possible to rebook online under certain circumstances. If it is not possible to rebook online because of the payment method, then you will be informed accordingly during the rebooking process).
|
||||
* There must be an active flight booking for your ticket. It is not possible to rebook open tickets or tickets without the corresponding flight segments online at the moment.
|
||||
* It is currently only possible to rebook outbound (one-way) tickets or return tickets with single flight routes (point-to-point).
|
||||
"""
|
||||
|
||||
|
||||
def search_flights(
|
||||
departure_airport: str = None,
|
||||
arrival_airport: str = None,
|
||||
start_time: str = None,
|
||||
end_time: str = None,
|
||||
) -> list[dict]:
|
||||
return """
|
||||
[{"flight_id": 19238, "flight_no": "LX0112", "scheduled_departure": "2024-05-08 12:09:03.561731-04:00", "scheduled_arrival": "2024-05-08 13:39:03.561731-04:00", "departure_airport": "CDG", "arrival_airport": "BSL", "status": "Scheduled", "aircraft_code": "SU9", "actual_departure": null, "actual_arrival": null}, {"flight_id": 19242, "flight_no": "LX0112", "scheduled_departure": "2024-05-09 12:09:03.561731-04:00", "scheduled_arrival": "2024-05-09 13:39:03.561731-04:00", "departure_airport": "CDG", "arrival_airport": "BSL", "status": "Scheduled", "aircraft_code": "SU9", "actual_departure": null, "actual_arrival": null}]"""
|
||||
|
||||
|
||||
def search_hotels(
|
||||
location: str = None,
|
||||
price_tier: str = None,
|
||||
checkin_date: str = None,
|
||||
checkout_date: str = None,
|
||||
) -> list[dict]:
|
||||
return """
|
||||
[{"id": 1, "name": "Hilton Basel", "location": "Basel", "price_tier": "Luxury"}, {"id": 3, "name": "Hyatt Regency Basel", "location": "Basel", "price_tier": "Upper Upscale"}, {"id": 8, "name": "Holiday Inn Basel", "location": "Basel", "price_tier": "Upper Midscale"}]
|
||||
"""
|
||||
|
||||
|
||||
def book_hotel(hotel_name: str) -> str:
|
||||
return 'OK, your hotel has been booked.'
|
||||
|
||||
|
||||
def before_model_call(agent: Agent, session: Session, user_message):
|
||||
if 'expedia' in user_message.lower():
|
||||
response = types.Content(
|
||||
role='model',
|
||||
parts=[types.Part(text="Sorry, I can't answer this question.")],
|
||||
)
|
||||
return response
|
||||
return None
|
||||
|
||||
|
||||
def after_model_call(
|
||||
agent: Agent, session: Session, content: types.Content
|
||||
) -> bool:
|
||||
model_message = content.parts[0].text
|
||||
if 'expedia' in model_message.lower():
|
||||
response = types.Content(
|
||||
role='model',
|
||||
parts=[types.Part(text="Sorry, I can't answer this question.")],
|
||||
)
|
||||
return response
|
||||
return None
|
||||
|
||||
|
||||
flight_agent = Agent(
|
||||
model='gemini-1.5-pro',
|
||||
name='flight_agent',
|
||||
description='Handles flight information, policy and updates',
|
||||
instruction="""
|
||||
You are a specialized assistant for handling flight updates.
|
||||
The primary assistant delegates work to you whenever the user needs help updating their bookings.
|
||||
Confirm the updated flight details with the customer and inform them of any additional fees.
|
||||
When searching, be persistent. Expand your query bounds if the first search returns no results.
|
||||
Remember that a booking isn't completed until after the relevant tool has successfully been used.
|
||||
Do not waste the user's time. Do not make up invalid tools or functions.
|
||||
""",
|
||||
tools=[
|
||||
list_customer_flights,
|
||||
lookup_company_policy,
|
||||
fetch_user_flight_information,
|
||||
search_flights,
|
||||
update_ticket_to_new_flight,
|
||||
],
|
||||
)
|
||||
|
||||
hotel_agent = Agent(
|
||||
model='gemini-1.5-pro',
|
||||
name='hotel_agent',
|
||||
description='Handles hotel information and booking',
|
||||
instruction="""
|
||||
You are a specialized assistant for handling hotel bookings.
|
||||
The primary assistant delegates work to you whenever the user needs help booking a hotel.
|
||||
Search for available hotels based on the user's preferences and confirm the booking details with the customer.
|
||||
When searching, be persistent. Expand your query bounds if the first search returns no results.
|
||||
""",
|
||||
tools=[search_hotels, book_hotel],
|
||||
)
|
||||
|
||||
|
||||
idea_agent = RemoteAgent(
|
||||
model='gemini-1.5-pro',
|
||||
name='idea_agent',
|
||||
description='Provide travel ideas base on the destination.',
|
||||
url='http://localhost:8000/agent/run',
|
||||
)
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
model='gemini-1.5-pro',
|
||||
name='root_agent',
|
||||
instruction="""
|
||||
You are a helpful customer support assistant for Swiss Airlines.
|
||||
""",
|
||||
sub_agents=[flight_agent, hotel_agent, idea_agent],
|
||||
flow='auto',
|
||||
examples=[
|
||||
Example(
|
||||
input=types.Content(
|
||||
role='user',
|
||||
parts=[types.Part(text='How were you built?')],
|
||||
),
|
||||
output=[
|
||||
types.Content(
|
||||
role='model',
|
||||
parts=[
|
||||
types.Part(
|
||||
text='I was built with the best agent framework.'
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -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,338 @@
|
||||
# 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 google.adk import Agent
|
||||
|
||||
# A lightweight in-memory mock database
|
||||
ORDER_DB = {
|
||||
"1": "FINISHED",
|
||||
"2": "CANCELED",
|
||||
"3": "PENDING",
|
||||
"4": "PENDING",
|
||||
} # Order id to status mapping. Available states: 'FINISHED', 'PENDING', and 'CANCELED'
|
||||
USER_TO_ORDER_DB = {
|
||||
"user_a": ["1", "4"],
|
||||
"user_b": ["2"],
|
||||
"user_c": ["3"],
|
||||
} # User id to Order id mapping
|
||||
TICKET_DB = [{
|
||||
"ticket_id": "1",
|
||||
"user_id": "user_a",
|
||||
"issue_type": "LOGIN_ISSUE",
|
||||
"status": "OPEN",
|
||||
}] # Available states: 'OPEN', 'CLOSED', 'ESCALATED'
|
||||
USER_INFO_DB = {
|
||||
"user_a": {"name": "Alice", "email": "alice@example.com"},
|
||||
"user_b": {"name": "Bob", "email": "bob@example.com"},
|
||||
}
|
||||
|
||||
|
||||
def reset_data():
|
||||
global ORDER_DB
|
||||
global USER_TO_ORDER_DB
|
||||
global TICKET_DB
|
||||
global USER_INFO_DB
|
||||
ORDER_DB = {
|
||||
"1": "FINISHED",
|
||||
"2": "CANCELED",
|
||||
"3": "PENDING",
|
||||
"4": "PENDING",
|
||||
}
|
||||
USER_TO_ORDER_DB = {
|
||||
"user_a": ["1", "4"],
|
||||
"user_b": ["2"],
|
||||
"user_c": ["3"],
|
||||
}
|
||||
TICKET_DB = [{
|
||||
"ticket_id": "1",
|
||||
"user_id": "user_a",
|
||||
"issue_type": "LOGIN_ISSUE",
|
||||
"status": "OPEN",
|
||||
}]
|
||||
USER_INFO_DB = {
|
||||
"user_a": {"name": "Alice", "email": "alice@example.com"},
|
||||
"user_b": {"name": "Bob", "email": "bob@example.com"},
|
||||
}
|
||||
|
||||
|
||||
def get_order_status(order_id: str) -> str:
|
||||
"""Get the status of an order.
|
||||
|
||||
Args:
|
||||
order_id (str): The unique identifier of the order.
|
||||
|
||||
Returns:
|
||||
str: The status of the order (e.g., 'FINISHED', 'CANCELED', 'PENDING'),
|
||||
or 'Order not found' if the order_id does not exist.
|
||||
"""
|
||||
return ORDER_DB.get(order_id, "Order not found")
|
||||
|
||||
|
||||
def get_order_ids_for_user(user_id: str) -> list:
|
||||
"""Get the list of order IDs assigned to a specific transaction associated with a user.
|
||||
|
||||
Args:
|
||||
user_id (str): The unique identifier of the user.
|
||||
|
||||
Returns:
|
||||
List[str]: A list of order IDs associated with the user, or an empty list
|
||||
if no orders are found.
|
||||
"""
|
||||
return USER_TO_ORDER_DB.get(user_id, [])
|
||||
|
||||
|
||||
def cancel_order(order_id: str) -> str:
|
||||
"""Cancel an order if it is in a 'PENDING' state.
|
||||
|
||||
You should call "get_order_status" to check the status first, before calling
|
||||
this tool.
|
||||
|
||||
Args:
|
||||
order_id (str): The unique identifier of the order to be canceled.
|
||||
|
||||
Returns:
|
||||
str: A message indicating whether the order was successfully canceled or
|
||||
not.
|
||||
"""
|
||||
if order_id in ORDER_DB and ORDER_DB[order_id] == "PENDING":
|
||||
ORDER_DB[order_id] = "CANCELED"
|
||||
return f"Order {order_id} has been canceled."
|
||||
return f"Order {order_id} cannot be canceled."
|
||||
|
||||
|
||||
def refund_order(order_id: str) -> str:
|
||||
"""Process a refund for an order if it is in a 'CANCELED' state.
|
||||
|
||||
You should call "get_order_status" to check if status first, before calling
|
||||
this tool.
|
||||
|
||||
Args:
|
||||
order_id (str): The unique identifier of the order to be refunded.
|
||||
|
||||
Returns:
|
||||
str: A message indicating whether the order was successfully refunded or
|
||||
not.
|
||||
"""
|
||||
if order_id in ORDER_DB and ORDER_DB[order_id] == "CANCELED":
|
||||
return f"Order {order_id} has been refunded."
|
||||
return f"Order {order_id} cannot be refunded."
|
||||
|
||||
|
||||
def create_ticket(user_id: str, issue_type: str) -> str:
|
||||
"""Create a new support ticket for a user.
|
||||
|
||||
Args:
|
||||
user_id (str): The unique identifier of the user creating the ticket.
|
||||
issue_type (str): An issue type the user is facing. Available types:
|
||||
'LOGIN_ISSUE', 'ORDER_ISSUE', 'OTHER'.
|
||||
|
||||
Returns:
|
||||
str: A message indicating that the ticket was created successfully,
|
||||
including the ticket ID.
|
||||
"""
|
||||
ticket_id = str(len(TICKET_DB) + 1)
|
||||
TICKET_DB.append({
|
||||
"ticket_id": ticket_id,
|
||||
"user_id": user_id,
|
||||
"issue_type": issue_type,
|
||||
"status": "OPEN",
|
||||
})
|
||||
return f"Ticket {ticket_id} created successfully."
|
||||
|
||||
|
||||
def get_ticket_info(ticket_id: str) -> str:
|
||||
"""Retrieve the information of a support ticket.
|
||||
|
||||
current status of a support ticket.
|
||||
|
||||
Args:
|
||||
ticket_id (str): The unique identifier of the ticket.
|
||||
|
||||
Returns:
|
||||
A dictionary contains the following fields, or 'Ticket not found' if the
|
||||
ticket_id does not exist:
|
||||
- "ticket_id": str, the current ticket id
|
||||
- "user_id": str, the associated user id
|
||||
- "issue": str, the issue type
|
||||
- "status": The current status of the ticket (e.g., 'OPEN', 'CLOSED',
|
||||
'ESCALATED')
|
||||
|
||||
Example: {"ticket_id": "1", "user_id": "user_a", "issue": "Login issue",
|
||||
"status": "OPEN"}
|
||||
"""
|
||||
for ticket in TICKET_DB:
|
||||
if ticket["ticket_id"] == ticket_id:
|
||||
return ticket
|
||||
return "Ticket not found"
|
||||
|
||||
|
||||
def get_tickets_for_user(user_id: str) -> list:
|
||||
"""Get all the ticket IDs associated with a user.
|
||||
|
||||
Args:
|
||||
user_id (str): The unique identifier of the user.
|
||||
|
||||
Returns:
|
||||
List[str]: A list of ticket IDs associated with the user.
|
||||
If no tickets are found, returns an empty list.
|
||||
"""
|
||||
return [
|
||||
ticket["ticket_id"]
|
||||
for ticket in TICKET_DB
|
||||
if ticket["user_id"] == user_id
|
||||
]
|
||||
|
||||
|
||||
def update_ticket_status(ticket_id: str, status: str) -> str:
|
||||
"""Update the status of a support ticket.
|
||||
|
||||
Args:
|
||||
ticket_id (str): The unique identifier of the ticket.
|
||||
status (str): The new status to assign to the ticket (e.g., 'OPEN',
|
||||
'CLOSED', 'ESCALATED').
|
||||
|
||||
Returns:
|
||||
str: A message indicating whether the ticket status was successfully
|
||||
updated.
|
||||
"""
|
||||
for ticket in TICKET_DB:
|
||||
if ticket["ticket_id"] == ticket_id:
|
||||
ticket["status"] = status
|
||||
return f"Ticket {ticket_id} status updated to {status}."
|
||||
return "Ticket not found"
|
||||
|
||||
|
||||
def get_user_info(user_id: str) -> dict:
|
||||
"""Retrieve information (name, email) about a user.
|
||||
|
||||
Args:
|
||||
user_id (str): The unique identifier of the user.
|
||||
|
||||
Returns:
|
||||
dict or str: A dictionary containing user information of the following
|
||||
fields, or 'User not found' if the user_id does not exist:
|
||||
|
||||
- name: The name of the user
|
||||
- email: The email address of the user
|
||||
|
||||
For example, {"name": "Chelsea", "email": "123@example.com"}
|
||||
"""
|
||||
return USER_INFO_DB.get(user_id, "User not found")
|
||||
|
||||
|
||||
def send_email(user_id: str, email: str) -> list:
|
||||
"""Send email to user for notification.
|
||||
|
||||
Args:
|
||||
user_id (str): The unique identifier of the user.
|
||||
email (str): The email address of the user.
|
||||
|
||||
Returns:
|
||||
str: A message indicating whether the email was successfully sent.
|
||||
"""
|
||||
if user_id in USER_INFO_DB:
|
||||
return f"Email sent to {email} for user id {user_id}"
|
||||
return "Cannot find this user"
|
||||
|
||||
|
||||
# def update_user_info(user_id: str, new_info: dict[str, str]) -> str:
|
||||
def update_user_info(user_id: str, email: str, name: str) -> str:
|
||||
"""Update a user's information.
|
||||
|
||||
Args:
|
||||
user_id (str): The unique identifier of the user.
|
||||
new_info (dict): A dictionary containing the fields to be updated (e.g.,
|
||||
{'email': 'new_email@example.com'}). Available field keys: 'email' and
|
||||
'name'.
|
||||
|
||||
Returns:
|
||||
str: A message indicating whether the user's information was successfully
|
||||
updated or not.
|
||||
"""
|
||||
if user_id in USER_INFO_DB:
|
||||
# USER_INFO_DB[user_id].update(new_info)
|
||||
if email and name:
|
||||
USER_INFO_DB[user_id].update({"email": email, "name": name})
|
||||
elif email:
|
||||
USER_INFO_DB[user_id].update({"email": email})
|
||||
elif name:
|
||||
USER_INFO_DB[user_id].update({"name": name})
|
||||
else:
|
||||
raise ValueError("this should not happen.")
|
||||
return f"User {user_id} information updated."
|
||||
return "User not found"
|
||||
|
||||
|
||||
def get_user_id_from_cookie() -> str:
|
||||
"""Get user ID(username) from the cookie.
|
||||
|
||||
Only use this function when you do not know user ID(username).
|
||||
|
||||
Args: None
|
||||
|
||||
Returns:
|
||||
str: The user ID.
|
||||
"""
|
||||
return "user_a"
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-2.0-flash-001",
|
||||
name="Ecommerce_Customer_Service",
|
||||
instruction="""
|
||||
You are an intelligent customer service assistant for an e-commerce platform. Your goal is to accurately understand user queries and use the appropriate tools to fulfill requests. Follow these guidelines:
|
||||
|
||||
1. **Understand the Query**:
|
||||
- Identify actions and conditions (e.g., create a ticket only for pending orders).
|
||||
- Extract necessary details (e.g., user ID, order ID) from the query or infer them from the context.
|
||||
|
||||
2. **Plan Multi-Step Workflows**:
|
||||
- Break down complex queries into sequential steps. For example
|
||||
- typical workflow:
|
||||
- Retrieve IDs or references first (e.g., orders for a user).
|
||||
- Evaluate conditions (e.g., check order status).
|
||||
- Perform actions (e.g., create a ticket) only when conditions are met.
|
||||
- another typical workflows - order cancellation and refund:
|
||||
- Retrieve all orders for the user (`get_order_ids_for_user`).
|
||||
- Cancel pending orders (`cancel_order`).
|
||||
- Refund canceled orders (`refund_order`).
|
||||
- Notify the user (`send_email`).
|
||||
- another typical workflows - send user report:
|
||||
- Get user id.
|
||||
- Get user info(like emails)
|
||||
- Send email to user.
|
||||
|
||||
3. **Avoid Skipping Steps**:
|
||||
- Ensure each intermediate step is completed before moving to the next.
|
||||
- Do not create tickets or take other actions without verifying the conditions specified in the query.
|
||||
|
||||
4. **Provide Clear Responses**:
|
||||
- Confirm the actions performed, including details like ticket ID or pending orders.
|
||||
- Ensure the response aligns with the steps taken and query intent.
|
||||
""",
|
||||
tools=[
|
||||
get_order_status,
|
||||
cancel_order,
|
||||
get_order_ids_for_user,
|
||||
refund_order,
|
||||
create_ticket,
|
||||
update_ticket_status,
|
||||
get_tickets_for_user,
|
||||
get_ticket_info,
|
||||
get_user_info,
|
||||
send_email,
|
||||
update_user_info,
|
||||
get_user_id_from_cookie,
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
[
|
||||
{
|
||||
"query": "Send an email to user user_a whose email address is alice@example.com",
|
||||
"expected_tool_use": [
|
||||
{
|
||||
"tool_name": "send_email",
|
||||
"tool_input": {
|
||||
"email": "alice@example.com",
|
||||
"user_id": "user_a"
|
||||
}
|
||||
}
|
||||
],
|
||||
"reference": "Email sent to alice@example.com for user id user_a."
|
||||
},
|
||||
{
|
||||
"query": "Can you tell me the status of my order with ID 1?",
|
||||
"expected_tool_use": [
|
||||
{
|
||||
"tool_name": "get_order_status",
|
||||
"tool_input": {
|
||||
"order_id": "1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"reference": "Your order with ID 1 is FINISHED."
|
||||
},
|
||||
{
|
||||
"query": "Cancel all pending order for the user with user id user_a",
|
||||
"expected_tool_use": [
|
||||
{
|
||||
"tool_name": "get_order_ids_for_user",
|
||||
"tool_input": {
|
||||
"user_id": "user_a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool_name": "get_order_status",
|
||||
"tool_input": {
|
||||
"order_id": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool_name": "get_order_status",
|
||||
"tool_input": {
|
||||
"order_id": "4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool_name": "cancel_order",
|
||||
"tool_input": {
|
||||
"order_id": "4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"reference": "I have checked your orders and order 4 was in pending status, so I have cancelled it. Order 1 was already finished and couldn't be cancelled.\n"
|
||||
},
|
||||
{
|
||||
"query": "What orders have I placed under the username user_b?",
|
||||
"expected_tool_use": [
|
||||
{
|
||||
"tool_name": "get_order_ids_for_user",
|
||||
"tool_input": {
|
||||
"user_id": "user_b"
|
||||
}
|
||||
}
|
||||
],
|
||||
"reference": "User user_b has placed one order with order ID 2.\n"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"criteria": {
|
||||
"tool_trajectory_avg_score": 0.7,
|
||||
"response_match_score": 0.5
|
||||
}
|
||||
}
|
||||
15
tests/integration/fixture/flow_complex_spark/__init__.py
Normal file
15
tests/integration/fixture/flow_complex_spark/__init__.py
Normal file
@@ -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
|
||||
182
tests/integration/fixture/flow_complex_spark/agent.py
Normal file
182
tests/integration/fixture/flow_complex_spark/agent.py
Normal file
@@ -0,0 +1,182 @@
|
||||
# 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 google.adk import Agent
|
||||
from google.genai import types
|
||||
|
||||
research_plan_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="research_plan_agent",
|
||||
description="I can help generate research plan.",
|
||||
instruction="""\
|
||||
Your task is to create a research plan according to the user's query.
|
||||
|
||||
# Here are the instructions for creating the research plan:
|
||||
|
||||
+ Focus on finding specific things, e.g. products, data, etc.
|
||||
+ Have the personality of a work colleague that is very helpful and explains things very nicely.
|
||||
+ Don't mention your name unless you are asked.
|
||||
+ Think about the most common things that you would need to research.
|
||||
+ Think about possible answers when creating the plan.
|
||||
+ Your task is to create the sections that should be researched. You will output high level headers, preceded by ##
|
||||
+ Underneath each header, write a short sentence on what we want to find there.
|
||||
+ The headers will follow the logical analysis pattern, as well as logical exploration pattern.
|
||||
+ The headers should be a statement, not be in the form of questions.
|
||||
+ The header will not include roman numerals or anything of the sort, e.g. ":", etc
|
||||
+ Do not include things that you cannot possibly know about from using Google Search: e.g. sales forecasting, competitors, profitability analysis, etc.
|
||||
+ Do not have an executive summary
|
||||
+ In each section describe specifically what will be researched.
|
||||
+ Never use "we will", but rather "I will".
|
||||
+ Don't ask for clarifications from the user.
|
||||
+ Do not ask the user for clarifications or if they have any other questions.
|
||||
+ All headers should be bolded.
|
||||
+ If you have steps in the plan that depend on other information, make sure they are 2 diferent sections in the plan.
|
||||
+ At the end mention that you will start researching.
|
||||
|
||||
# Instruction on replying format
|
||||
|
||||
+ Start with your name as "[research_plan_agent]: ".
|
||||
+ Output the content you want to say.
|
||||
|
||||
Output summary:
|
||||
""",
|
||||
flow="single",
|
||||
sub_agents=[],
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
temperature=0.1,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
question_generation_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="question_generation_agent",
|
||||
description="I can help generate questions related to user's question.",
|
||||
instruction="""\
|
||||
Generate questions related to the research plan generated by research_plan_agent.
|
||||
|
||||
# Instruction on replying format
|
||||
|
||||
Your reply should be a numbered lsit.
|
||||
|
||||
For each question, reply in the following format: "[question_generation_agent]: [generated questions]"
|
||||
|
||||
Here is an example of the generated question list:
|
||||
|
||||
1. [question_generation_agent]: which state is San Jose in?
|
||||
2. [question_generation_agent]: how google website is designed?
|
||||
""",
|
||||
flow="single",
|
||||
sub_agents=[],
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
temperature=0.1,
|
||||
),
|
||||
)
|
||||
|
||||
information_retrieval_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="information_retrieval_agent",
|
||||
description=(
|
||||
"I can help retrieve information related to question_generation_agent's"
|
||||
" question."
|
||||
),
|
||||
instruction="""\
|
||||
Inspect all the questions after "[question_generation_agent]: " and asnwer them.
|
||||
|
||||
# Instruction on replying format
|
||||
|
||||
Always start with "[information_retrieval_agent]: "
|
||||
|
||||
For the answer of one question:
|
||||
|
||||
- Start with a title with one line summary of the reply.
|
||||
- The title line should be bolded and starts with No.x of the corresponding question.
|
||||
- Have a paragraph of detailed explain.
|
||||
|
||||
# Instruction on exiting the loop
|
||||
|
||||
- If you see there are less than 20 questions by "question_generation_agent", do not say "[exit]".
|
||||
- If you see there are already great or equal to 20 questions asked by "question_generation_agent", say "[exit]" at last to exit the loop.
|
||||
""",
|
||||
flow="single",
|
||||
sub_agents=[],
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
temperature=0.1,
|
||||
),
|
||||
)
|
||||
|
||||
question_sources_generation_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="question_sources_generation_agent",
|
||||
description=(
|
||||
"I can help generate questions and retrieve related information."
|
||||
),
|
||||
instruction="Generate questions and retrieve information.",
|
||||
flow="loop",
|
||||
sub_agents=[
|
||||
question_generation_agent,
|
||||
information_retrieval_agent,
|
||||
],
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
temperature=0.1,
|
||||
),
|
||||
)
|
||||
|
||||
summary_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="summary_agent",
|
||||
description="I can help summarize information of previous content.",
|
||||
instruction="""\
|
||||
Summarize information in all historical messages that were replied by "question_generation_agent" and "information_retrieval_agent".
|
||||
|
||||
# Instruction on replying format
|
||||
|
||||
- The output should be like an essay that has a title, an abstract, multiple paragraphs for each topic and a conclusion.
|
||||
- Each paragraph should maps to one or more question in historical content.
|
||||
""",
|
||||
flow="single",
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
temperature=0.8,
|
||||
),
|
||||
)
|
||||
|
||||
research_assistant = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="research_assistant",
|
||||
description="I can help with research question.",
|
||||
instruction="Help customers with their need.",
|
||||
flow="sequential",
|
||||
sub_agents=[
|
||||
research_plan_agent,
|
||||
question_sources_generation_agent,
|
||||
summary_agent,
|
||||
],
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
temperature=0.1,
|
||||
),
|
||||
)
|
||||
|
||||
spark_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="spark_assistant",
|
||||
description="I can help with non-research question.",
|
||||
instruction="Help customers with their need.",
|
||||
flow="auto",
|
||||
sub_agents=[research_assistant],
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
temperature=0.1,
|
||||
),
|
||||
)
|
||||
|
||||
root_agent = spark_agent
|
||||
190
tests/integration/fixture/flow_complex_spark/sample.session.json
Normal file
190
tests/integration/fixture/flow_complex_spark/sample.session.json
Normal file
File diff suppressed because one or more lines are too long
15
tests/integration/fixture/hello_world_agent/__init__.py
Normal file
15
tests/integration/fixture/hello_world_agent/__init__.py
Normal file
@@ -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
|
||||
95
tests/integration/fixture/hello_world_agent/agent.py
Normal file
95
tests/integration/fixture/hello_world_agent/agent.py
Normal file
@@ -0,0 +1,95 @@
|
||||
# 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.
|
||||
|
||||
# Hello world agent from agent 1.0 - https://colab.sandbox.google.com/drive/1Zq-nqmgK0nCERCv8jKIaoeTTgbNn6oSo?resourcekey=0-GYaz9pFT4wY8CI8Cvjy5GA#scrollTo=u3X3XwDOaCv9
|
||||
import random
|
||||
|
||||
from google.adk import Agent
|
||||
from google.genai import types
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def check_prime(nums: list[int]) -> list[str]:
|
||||
"""Check if a given list of numbers are prime.
|
||||
|
||||
Args:
|
||||
nums: The list of numbers to check.
|
||||
|
||||
Returns:
|
||||
A str indicating which number is prime.
|
||||
"""
|
||||
primes = set()
|
||||
for number in nums:
|
||||
number = int(number)
|
||||
if number <= 1:
|
||||
continue
|
||||
is_prime = True
|
||||
for i in range(2, int(number**0.5) + 1):
|
||||
if number % i == 0:
|
||||
is_prime = False
|
||||
break
|
||||
if is_prime:
|
||||
primes.add(number)
|
||||
return (
|
||||
'No prime numbers found.'
|
||||
if not primes
|
||||
else f"{', '.join(str(num) for num in primes)} are prime numbers."
|
||||
)
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
model='gemini-2.0-flash-001',
|
||||
name='data_processing_agent',
|
||||
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).
|
||||
The only things you do are roll dice for the user and discuss the outcomes.
|
||||
It is ok to discuss previous dice roles, and comment on the dice rolls.
|
||||
When you are asked to roll a die, you must call the roll_die tool with the number of sides. Be sure to pass in an integer. Do not pass in a string.
|
||||
You should never roll a die on your own.
|
||||
When checking prime numbers, call the check_prime tool with a list of integers. Be sure to pass in a list of integers. You should never pass in a string.
|
||||
You should not check prime numbers before calling the tool.
|
||||
When you are asked to roll a die and check prime numbers, you should always make the following two function calls:
|
||||
1. You should first call the roll_die tool to get a roll. Wait for the function response before calling the check_prime tool.
|
||||
2. After you get the function response from roll_die tool, you should call the check_prime tool with the roll_die result.
|
||||
2.1 If user asks you to check primes based on previous rolls, make sure you include the previous rolls in the list.
|
||||
3. When you respond, you must include the roll_die result from step 1.
|
||||
You should always perform the previous 3 steps when asking for a roll and checking prime numbers.
|
||||
You should not rely on the previous history on prime results.
|
||||
""",
|
||||
tools=[
|
||||
roll_die,
|
||||
check_prime,
|
||||
],
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
safety_settings=[
|
||||
types.SafetySetting( # avoid false alarm about rolling dice.
|
||||
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
|
||||
threshold=types.HarmBlockThreshold.OFF,
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
[
|
||||
{
|
||||
"query": "Hi who are you?",
|
||||
"expected_tool_use": [],
|
||||
"reference": "I am a data processing agent. I can roll dice and check if the results are prime numbers. What would you like me to do? \n"
|
||||
},
|
||||
{
|
||||
"query": "What can you do?",
|
||||
"expected_tool_use": [],
|
||||
"reference": "I can roll dice for you of different sizes, and I can check if the results are prime numbers. I can also remember previous rolls if you'd like to check those for primes as well. What would you like me to do? \n"
|
||||
},
|
||||
{
|
||||
"query": "Can you roll a die with 6 sides",
|
||||
"expected_tool_use": [
|
||||
{
|
||||
"tool_name": "roll_die",
|
||||
"tool_input": {
|
||||
"sides": 6
|
||||
}
|
||||
}
|
||||
],
|
||||
"reference": null
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"criteria": {
|
||||
"tool_trajectory_avg_score": 1.0,
|
||||
"response_match_score": 0.5
|
||||
}
|
||||
}
|
||||
15
tests/integration/fixture/home_automation_agent/__init__.py
Normal file
15
tests/integration/fixture/home_automation_agent/__init__.py
Normal file
@@ -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
|
||||
304
tests/integration/fixture/home_automation_agent/agent.py
Normal file
304
tests/integration/fixture/home_automation_agent/agent.py
Normal file
@@ -0,0 +1,304 @@
|
||||
# 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 sys
|
||||
|
||||
from google.adk import Agent
|
||||
|
||||
DEVICE_DB = {
|
||||
"device_1": {"status": "ON", "location": "Living Room"},
|
||||
"device_2": {"status": "OFF", "location": "Bedroom"},
|
||||
"device_3": {"status": "OFF", "location": "Kitchen"},
|
||||
}
|
||||
|
||||
TEMPERATURE_DB = {
|
||||
"Living Room": 22,
|
||||
"Bedroom": 20,
|
||||
"Kitchen": 24,
|
||||
}
|
||||
|
||||
SCHEDULE_DB = {
|
||||
"device_1": {"time": "18:00", "status": "ON"},
|
||||
"device_2": {"time": "22:00", "status": "OFF"},
|
||||
}
|
||||
|
||||
USER_PREFERENCES_DB = {
|
||||
"user_x": {"preferred_temp": 21, "location": "Bedroom"},
|
||||
"user_x": {"preferred_temp": 21, "location": "Living Room"},
|
||||
"user_y": {"preferred_temp": 23, "location": "Living Room"},
|
||||
}
|
||||
|
||||
|
||||
def reset_data():
|
||||
global DEVICE_DB
|
||||
global TEMPERATURE_DB
|
||||
global SCHEDULE_DB
|
||||
global USER_PREFERENCES_DB
|
||||
DEVICE_DB = {
|
||||
"device_1": {"status": "ON", "location": "Living Room"},
|
||||
"device_2": {"status": "OFF", "location": "Bedroom"},
|
||||
"device_3": {"status": "OFF", "location": "Kitchen"},
|
||||
}
|
||||
|
||||
TEMPERATURE_DB = {
|
||||
"Living Room": 22,
|
||||
"Bedroom": 20,
|
||||
"Kitchen": 24,
|
||||
}
|
||||
|
||||
SCHEDULE_DB = {
|
||||
"device_1": {"time": "18:00", "status": "ON"},
|
||||
"device_2": {"time": "22:00", "status": "OFF"},
|
||||
}
|
||||
|
||||
USER_PREFERENCES_DB = {
|
||||
"user_x": {"preferred_temp": 21, "location": "Bedroom"},
|
||||
"user_x": {"preferred_temp": 21, "location": "Living Room"},
|
||||
"user_y": {"preferred_temp": 23, "location": "Living Room"},
|
||||
}
|
||||
|
||||
|
||||
def get_device_info(device_id: str) -> dict:
|
||||
"""Get the current status and location of a AC device.
|
||||
|
||||
Args:
|
||||
device_id (str): The unique identifier of the device.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the following fields, or 'Device not found'
|
||||
if the device_id does not exist:
|
||||
- status: The current status of the device (e.g., 'ON', 'OFF')
|
||||
- location: The location where the device is installed (e.g., 'Living
|
||||
Room', 'Bedroom', ''Kitchen')
|
||||
"""
|
||||
return DEVICE_DB.get(device_id, "Device not found")
|
||||
|
||||
|
||||
# def set_device_info(device_id: str, updates: dict) -> str:
|
||||
# """Update the information of a AC device, specifically its status and/or location.
|
||||
|
||||
# Args:
|
||||
# device_id (str): Required. The unique identifier of the device.
|
||||
# updates (dict): Required. A dictionary containing the fields to be
|
||||
# updated. Supported keys: - "status" (str): The new status to set for the
|
||||
# device. Accepted values: 'ON', 'OFF'. **Only these values are allowed.**
|
||||
# - "location" (str): The new location to set for the device. Accepted
|
||||
# values: 'Living Room', 'Bedroom', 'Kitchen'. **Only these values are
|
||||
# allowed.**
|
||||
|
||||
|
||||
# Returns:
|
||||
# str: A message indicating whether the device information was successfully
|
||||
# updated.
|
||||
# """
|
||||
# if device_id in DEVICE_DB:
|
||||
# if "status" in updates:
|
||||
# DEVICE_DB[device_id]["status"] = updates["status"]
|
||||
# if "location" in updates:
|
||||
# DEVICE_DB[device_id]["location"] = updates["location"]
|
||||
# return f"Device {device_id} information updated: {updates}."
|
||||
# return "Device not found"
|
||||
def set_device_info(
|
||||
device_id: str, status: str = "", location: str = ""
|
||||
) -> str:
|
||||
"""Update the information of a AC device, specifically its status and/or location.
|
||||
|
||||
Args:
|
||||
device_id (str): Required. The unique identifier of the device.
|
||||
status (str): The new status to set for the
|
||||
device. Accepted values: 'ON', 'OFF'. **Only these values are allowed.**
|
||||
location (str): The new location to set for the device. Accepted
|
||||
values: 'Living Room', 'Bedroom', 'Kitchen'. **Only these values are
|
||||
allowed.**
|
||||
|
||||
Returns:
|
||||
str: A message indicating whether the device information was successfully
|
||||
updated.
|
||||
"""
|
||||
if device_id in DEVICE_DB:
|
||||
if status:
|
||||
DEVICE_DB[device_id]["status"] = status
|
||||
return f"Device {device_id} information updated: status -> {status}."
|
||||
if location:
|
||||
DEVICE_DB[device_id]["location"] = location
|
||||
return f"Device {device_id} information updated: location -> {location}."
|
||||
return "Device not found"
|
||||
|
||||
|
||||
def get_temperature(location: str) -> int:
|
||||
"""Get the current temperature in celsius of a location (e.g., 'Living Room', 'Bedroom', 'Kitchen').
|
||||
|
||||
Args:
|
||||
location (str): The location for which to retrieve the temperature (e.g.,
|
||||
'Living Room', 'Bedroom', 'Kitchen').
|
||||
|
||||
Returns:
|
||||
int: The current temperature in celsius in the specified location, or
|
||||
'Location not found' if the location does not exist.
|
||||
"""
|
||||
return TEMPERATURE_DB.get(location, "Location not found")
|
||||
|
||||
|
||||
def set_temperature(location: str, temperature: int) -> str:
|
||||
"""Set the desired temperature in celsius for a location.
|
||||
|
||||
Acceptable range of temperature: 18-30 celsius. If it's out of the range, do
|
||||
not call this tool.
|
||||
|
||||
Args:
|
||||
location (str): The location where the temperature should be set.
|
||||
temperature (int): The desired temperature as integer to set in celsius.
|
||||
Acceptable range: 18-30 celsius.
|
||||
|
||||
Returns:
|
||||
str: A message indicating whether the temperature was successfully set.
|
||||
"""
|
||||
if location in TEMPERATURE_DB:
|
||||
TEMPERATURE_DB[location] = temperature
|
||||
return f"Temperature in {location} set to {temperature}°C."
|
||||
return "Location not found"
|
||||
|
||||
|
||||
def get_user_preferences(user_id: str) -> dict:
|
||||
"""Get the temperature preferences and preferred location of a user_id.
|
||||
|
||||
user_id must be provided.
|
||||
|
||||
Args:
|
||||
user_id (str): The unique identifier of the user.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the following fields, or 'User not found' if
|
||||
the user_id does not exist:
|
||||
- preferred_temp: The user's preferred temperature.
|
||||
- location: The location where the user prefers to be.
|
||||
"""
|
||||
return USER_PREFERENCES_DB.get(user_id, "User not found")
|
||||
|
||||
|
||||
def set_device_schedule(device_id: str, time: str, status: str) -> str:
|
||||
"""Schedule a device to change its status at a specific time.
|
||||
|
||||
Args:
|
||||
device_id (str): The unique identifier of the device.
|
||||
time (str): The time at which the device should change its status (format:
|
||||
'HH:MM').
|
||||
status (str): The status to set for the device at the specified time
|
||||
(e.g., 'ON', 'OFF').
|
||||
|
||||
Returns:
|
||||
str: A message indicating whether the schedule was successfully set.
|
||||
"""
|
||||
if device_id in DEVICE_DB:
|
||||
SCHEDULE_DB[device_id] = {"time": time, "status": status}
|
||||
return f"Device {device_id} scheduled to turn {status} at {time}."
|
||||
return "Device not found"
|
||||
|
||||
|
||||
def get_device_schedule(device_id: str) -> dict:
|
||||
"""Retrieve the schedule of a device.
|
||||
|
||||
Args:
|
||||
device_id (str): The unique identifier of the device.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the following fields, or 'Schedule not
|
||||
found' if the device_id does not exist:
|
||||
- time: The scheduled time for the device to change its status (format:
|
||||
'HH:MM').
|
||||
- status: The status that will be set at the scheduled time (e.g., 'ON',
|
||||
'OFF').
|
||||
"""
|
||||
return SCHEDULE_DB.get(device_id, "Schedule not found")
|
||||
|
||||
|
||||
def celsius_to_fahrenheit(celsius: int) -> float:
|
||||
"""Convert Celsius to Fahrenheit.
|
||||
|
||||
You must call this to do the conversion of temperature, so you can get the
|
||||
precise number in required format.
|
||||
|
||||
Args:
|
||||
celsius (int): Temperature in Celsius.
|
||||
|
||||
Returns:
|
||||
float: Temperature in Fahrenheit.
|
||||
"""
|
||||
return (celsius * 9 / 5) + 32
|
||||
|
||||
|
||||
def fahrenheit_to_celsius(fahrenheit: float) -> int:
|
||||
"""Convert Fahrenheit to Celsius.
|
||||
|
||||
You must call this to do the conversion of temperature, so you can get the
|
||||
precise number in required format.
|
||||
|
||||
Args:
|
||||
fahrenheit (float): Temperature in Fahrenheit.
|
||||
|
||||
Returns:
|
||||
int: Temperature in Celsius.
|
||||
"""
|
||||
return int((fahrenheit - 32) * 5 / 9)
|
||||
|
||||
|
||||
def list_devices(status: str = "", location: str = "") -> list:
|
||||
"""Retrieve a list of AC devices, filtered by status and/or location when provided.
|
||||
|
||||
For cost efficiency, always apply as many filters (status and location) as
|
||||
available in the input arguments.
|
||||
|
||||
Args:
|
||||
status (str, optional): The status to filter devices by (e.g., 'ON',
|
||||
'OFF'). Defaults to None.
|
||||
location (str, optional): The location to filter devices by (e.g., 'Living
|
||||
Room', 'Bedroom', ''Kitchen'). Defaults to None.
|
||||
|
||||
Returns:
|
||||
list: A list of dictionaries, each containing the device ID, status, and
|
||||
location, or an empty list if no devices match the criteria.
|
||||
"""
|
||||
devices = []
|
||||
for device_id, info in DEVICE_DB.items():
|
||||
if ((not status) or info["status"] == status) and (
|
||||
(not location) or info["location"] == location
|
||||
):
|
||||
devices.append({
|
||||
"device_id": device_id,
|
||||
"status": info["status"],
|
||||
"location": info["location"],
|
||||
})
|
||||
return devices if devices else "No devices found matching the criteria."
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-2.0-flash-001",
|
||||
name="Home_automation_agent",
|
||||
instruction="""
|
||||
You are Home Automation Agent. You are responsible for controlling the devices in the home.
|
||||
""",
|
||||
tools=[
|
||||
get_device_info,
|
||||
set_device_info,
|
||||
get_temperature,
|
||||
set_temperature,
|
||||
get_user_preferences,
|
||||
set_device_schedule,
|
||||
get_device_schedule,
|
||||
celsius_to_fahrenheit,
|
||||
fahrenheit_to_celsius,
|
||||
list_devices,
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
[{
|
||||
"query": "Turn off device_2 in the Bedroom.",
|
||||
"expected_tool_use": [{"tool_name": "set_device_info", "tool_input": {"location": "Bedroom", "device_id": "device_2", "status": "OFF"}}],
|
||||
"reference": "I have set the device_2 status to off."
|
||||
}]
|
||||
@@ -0,0 +1,5 @@
|
||||
[{
|
||||
"query": "Turn off device_3 in the Bedroom.",
|
||||
"expected_tool_use": [{"tool_name": "set_device_info", "tool_input": {"location": "Bedroom", "device_id": "device_3", "status": "OFF"}}],
|
||||
"reference": "I have set the device_3 status to off."
|
||||
}]
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"criteria": {
|
||||
"tool_trajectory_avg_score": 1.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"query": "Turn off device_2 in the Bedroom.",
|
||||
"expected_tool_use": [{
|
||||
"tool_name": "set_device_info",
|
||||
"tool_input": {"location": "Bedroom", "status": "OFF", "device_id": "device_2"}
|
||||
}],
|
||||
"reference": "I have set the device 2 status to off."
|
||||
},
|
||||
{
|
||||
"query": "What's the status of device_2 in the Bedroom?",
|
||||
"expected_tool_use": [{
|
||||
"tool_name": "get_device_info",
|
||||
"tool_input": {"device_id": "device_2"}
|
||||
}],
|
||||
"reference": "Status of device_2 is off."
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"query": "Turn off device_2 in the Bedroom.",
|
||||
"expected_tool_use": [
|
||||
{
|
||||
"tool_name": "set_device_info",
|
||||
"tool_input": {"location": "Bedroom", "device_id": "device_2", "status": "OFF"}
|
||||
}
|
||||
],
|
||||
"reference": "OK. I've turned off device_2 in the Bedroom. Anything else?\n"
|
||||
},
|
||||
{
|
||||
"query": "What's the command I just issued?",
|
||||
"expected_tool_use": [],
|
||||
"reference": "You asked me to turn off device_2 in the Bedroom.\n"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"criteria": {
|
||||
"tool_trajectory_avg_score": 1.0,
|
||||
"response_match_score": 0.5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"query": "Turn off device_2 in the Bedroom.",
|
||||
"expected_tool_use": [{
|
||||
"tool_name": "set_device_info",
|
||||
"tool_input": {"location": "Bedroom", "device_id": "device_2", "status": "OFF"}
|
||||
}],
|
||||
"reference": "I have set the device 2 status to off."
|
||||
},
|
||||
{
|
||||
"query": "Turn on device_2 in the Bedroom.",
|
||||
"expected_tool_use": [{
|
||||
"tool_name": "set_device_info",
|
||||
"tool_input": {"location": "Bedroom", "status": "ON", "device_id": "device_2"}
|
||||
}],
|
||||
"reference": "I have set the device 2 status to on."
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"query": "Turn off device_2 in the Bedroom.",
|
||||
"expected_tool_use": [
|
||||
{
|
||||
"tool_name": "set_device_info",
|
||||
"tool_input": {"location": "Bedroom", "device_id": "device_2", "status": "OFF"}
|
||||
}
|
||||
],
|
||||
"reference": "OK. I've turned off device_2 in the Bedroom. Anything else?\n"
|
||||
},
|
||||
{
|
||||
"query": "What's the command I just issued?",
|
||||
"expected_tool_use": [],
|
||||
"reference": "You asked me to turn off device_2 in the Bedroom.\n"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
[{
|
||||
"query": "Turn off device_3 in the Bedroom.",
|
||||
"expected_tool_use": [{"tool_name": "set_device_info", "tool_input": {"location": "Bedroom", "device_id": "device_3", "status": "OFF"}}],
|
||||
"reference": "I have set the device_3 status to off."
|
||||
}]
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"criteria": {
|
||||
"tool_trajectory_avg_score": 1.0
|
||||
}
|
||||
}
|
||||
15
tests/integration/fixture/tool_agent/__init__.py
Normal file
15
tests/integration/fixture/tool_agent/__init__.py
Normal file
@@ -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
|
||||
218
tests/integration/fixture/tool_agent/agent.py
Normal file
218
tests/integration/fixture/tool_agent/agent.py
Normal file
@@ -0,0 +1,218 @@
|
||||
# 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 typing import Any
|
||||
|
||||
from crewai_tools import DirectoryReadTool
|
||||
from google.adk import Agent
|
||||
from google.adk.tools.agent_tool import AgentTool
|
||||
from google.adk.tools.crewai_tool import CrewaiTool
|
||||
from google.adk.tools.langchain_tool import LangchainTool
|
||||
from google.adk.tools.retrieval.files_retrieval import FilesRetrieval
|
||||
from google.adk.tools.retrieval.vertex_ai_rag_retrieval import VertexAiRagRetrieval
|
||||
from langchain_community.tools import ShellTool
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class TestCase(BaseModel):
|
||||
case: str
|
||||
|
||||
|
||||
class Test(BaseModel):
|
||||
test_title: list[str]
|
||||
|
||||
|
||||
def simple_function(param: str) -> str:
|
||||
if isinstance(param, str):
|
||||
return "Called simple function successfully"
|
||||
return "Called simple function with wrong param type"
|
||||
|
||||
|
||||
def no_param_function() -> str:
|
||||
return "Called no param function successfully"
|
||||
|
||||
|
||||
def no_output_function(param: str):
|
||||
return
|
||||
|
||||
|
||||
def multiple_param_types_function(
|
||||
param1: str, param2: int, param3: float, param4: bool
|
||||
) -> str:
|
||||
if (
|
||||
isinstance(param1, str)
|
||||
and isinstance(param2, int)
|
||||
and isinstance(param3, float)
|
||||
and isinstance(param4, bool)
|
||||
):
|
||||
return "Called multiple param types function successfully"
|
||||
return "Called multiple param types function with wrong param types"
|
||||
|
||||
|
||||
def throw_error_function(param: str) -> str:
|
||||
raise ValueError("Error thrown by throw_error_function")
|
||||
|
||||
|
||||
def list_str_param_function(param: list[str]) -> str:
|
||||
if isinstance(param, list) and all(isinstance(item, str) for item in param):
|
||||
return "Called list str param function successfully"
|
||||
return "Called list str param function with wrong param type"
|
||||
|
||||
|
||||
def return_list_str_function(param: str) -> list[str]:
|
||||
return ["Called return list str function successfully"]
|
||||
|
||||
|
||||
def complex_function_list_dict(
|
||||
param1: dict[str, Any], param2: list[dict[str, Any]]
|
||||
) -> list[Test]:
|
||||
if (
|
||||
isinstance(param1, dict)
|
||||
and isinstance(param2, list)
|
||||
and all(isinstance(item, dict) for item in param2)
|
||||
):
|
||||
return [
|
||||
Test(test_title=["function test 1", "function test 2"]),
|
||||
Test(test_title=["retrieval test"]),
|
||||
]
|
||||
raise ValueError("Wrong param")
|
||||
|
||||
|
||||
def repetive_call_1(param: str):
|
||||
return f"Call repetive_call_2 tool with param {param + '_repetive'}"
|
||||
|
||||
|
||||
def repetive_call_2(param: str):
|
||||
return param
|
||||
|
||||
|
||||
test_case_retrieval = FilesRetrieval(
|
||||
name="test_case_retrieval",
|
||||
description="General guidence for agent test cases",
|
||||
input_dir=os.path.join(os.path.dirname(__file__), "files"),
|
||||
)
|
||||
|
||||
valid_rag_retrieval = VertexAiRagRetrieval(
|
||||
name="valid_rag_retrieval",
|
||||
rag_corpora=[
|
||||
"projects/1096655024998/locations/us-central1/ragCorpora/4985766262475849728"
|
||||
],
|
||||
description="General guidence for agent test cases",
|
||||
)
|
||||
|
||||
invalid_rag_retrieval = VertexAiRagRetrieval(
|
||||
name="invalid_rag_retrieval",
|
||||
rag_corpora=[
|
||||
"projects/1096655024998/locations/us-central1/InValidRagCorporas/4985766262475849728"
|
||||
],
|
||||
description="Invalid rag retrieval resource name",
|
||||
)
|
||||
|
||||
non_exist_rag_retrieval = VertexAiRagRetrieval(
|
||||
name="non_exist_rag_retrieval",
|
||||
rag_corpora=[
|
||||
"projects/1096655024998/locations/us-central1/RagCorpora/1234567"
|
||||
],
|
||||
description="Non exist rag retrieval resource name",
|
||||
)
|
||||
|
||||
shell_tool = LangchainTool(ShellTool())
|
||||
|
||||
docs_tool = CrewaiTool(
|
||||
name="direcotry_read_tool",
|
||||
description="use this to find files for you.",
|
||||
tool=DirectoryReadTool(directory="."),
|
||||
)
|
||||
|
||||
no_schema_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="no_schema_agent",
|
||||
instruction="""Just say 'Hi'
|
||||
""",
|
||||
)
|
||||
|
||||
schema_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="schema_agent",
|
||||
instruction="""
|
||||
You will be given a test case.
|
||||
Return a list of the received test case appended with '_success' and '_failure' as test_titles
|
||||
""",
|
||||
input_schema=TestCase,
|
||||
output_schema=Test,
|
||||
)
|
||||
|
||||
no_input_schema_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="no_input_schema_agent",
|
||||
instruction="""
|
||||
Just return ['Tools_success, Tools_failure']
|
||||
""",
|
||||
output_schema=Test,
|
||||
)
|
||||
|
||||
no_output_schema_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="no_output_schema_agent",
|
||||
instruction="""
|
||||
Just say 'Hi'
|
||||
""",
|
||||
input_schema=TestCase,
|
||||
)
|
||||
|
||||
single_function_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="single_function_agent",
|
||||
description="An agent that calls a single function",
|
||||
instruction="When calling tools, just return what the tool returns.",
|
||||
tools=[simple_function],
|
||||
)
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="tool_agent",
|
||||
description="An agent that can call other tools",
|
||||
instruction="When calling tools, just return what the tool returns.",
|
||||
tools=[
|
||||
simple_function,
|
||||
no_param_function,
|
||||
no_output_function,
|
||||
multiple_param_types_function,
|
||||
throw_error_function,
|
||||
list_str_param_function,
|
||||
return_list_str_function,
|
||||
# complex_function_list_dict,
|
||||
repetive_call_1,
|
||||
repetive_call_2,
|
||||
test_case_retrieval,
|
||||
valid_rag_retrieval,
|
||||
invalid_rag_retrieval,
|
||||
non_exist_rag_retrieval,
|
||||
shell_tool,
|
||||
docs_tool,
|
||||
AgentTool(
|
||||
agent=no_schema_agent,
|
||||
),
|
||||
AgentTool(
|
||||
agent=schema_agent,
|
||||
),
|
||||
AgentTool(
|
||||
agent=no_input_schema_agent,
|
||||
),
|
||||
AgentTool(
|
||||
agent=no_output_schema_agent,
|
||||
),
|
||||
],
|
||||
)
|
||||
BIN
tests/integration/fixture/tool_agent/files/Agent_test_plan.pdf
Normal file
BIN
tests/integration/fixture/tool_agent/files/Agent_test_plan.pdf
Normal file
Binary file not shown.
15
tests/integration/fixture/trip_planner_agent/__init__.py
Normal file
15
tests/integration/fixture/trip_planner_agent/__init__.py
Normal file
@@ -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
|
||||
110
tests/integration/fixture/trip_planner_agent/agent.py
Normal file
110
tests/integration/fixture/trip_planner_agent/agent.py
Normal file
@@ -0,0 +1,110 @@
|
||||
# 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.
|
||||
|
||||
# https://github.com/crewAIInc/crewAI-examples/tree/main/trip_planner
|
||||
|
||||
from google.adk import Agent
|
||||
|
||||
# Agent that selects the best city for the trip.
|
||||
identify_agent = Agent(
|
||||
name='identify_agent',
|
||||
description='Select the best city based on weather, season, and prices.',
|
||||
instruction="""
|
||||
Analyze and select the best city for the trip based
|
||||
on specific criteria such as weather patterns, seasonal
|
||||
events, and travel costs. This task involves comparing
|
||||
multiple cities, considering factors like current weather
|
||||
conditions, upcoming cultural or seasonal events, and
|
||||
overall travel expenses.
|
||||
|
||||
Your final answer must be a detailed
|
||||
report on the chosen city, and everything you found out
|
||||
about it, including the actual flight costs, weather
|
||||
forecast and attractions.
|
||||
|
||||
Traveling from: {origin}
|
||||
City Options: {cities}
|
||||
Trip Date: {range}
|
||||
Traveler Interests: {interests}
|
||||
""",
|
||||
)
|
||||
|
||||
# Agent that gathers information about the city.
|
||||
gather_agent = Agent(
|
||||
name='gather_agent',
|
||||
description='Provide the BEST insights about the selected city',
|
||||
instruction="""
|
||||
As a local expert on this city you must compile an
|
||||
in-depth guide for someone traveling there and wanting
|
||||
to have THE BEST trip ever!
|
||||
Gather information about key attractions, local customs,
|
||||
special events, and daily activity recommendations.
|
||||
Find the best spots to go to, the kind of place only a
|
||||
local would know.
|
||||
This guide should provide a thorough overview of what
|
||||
the city has to offer, including hidden gems, cultural
|
||||
hotspots, must-visit landmarks, weather forecasts, and
|
||||
high level costs.
|
||||
|
||||
The final answer must be a comprehensive city guide,
|
||||
rich in cultural insights and practical tips,
|
||||
tailored to enhance the travel experience.
|
||||
|
||||
Trip Date: {range}
|
||||
Traveling from: {origin}
|
||||
Traveler Interests: {interests}
|
||||
""",
|
||||
)
|
||||
|
||||
# Agent that plans the trip.
|
||||
plan_agent = Agent(
|
||||
name='plan_agent',
|
||||
description="""Create the most amazing travel itineraries with budget and
|
||||
packing suggestions for the city""",
|
||||
instruction="""
|
||||
Expand this guide into a full 7-day travel
|
||||
itinerary with detailed per-day plans, including
|
||||
weather forecasts, places to eat, packing suggestions,
|
||||
and a budget breakdown.
|
||||
|
||||
You MUST suggest actual places to visit, actual hotels
|
||||
to stay and actual restaurants to go to.
|
||||
|
||||
This itinerary should cover all aspects of the trip,
|
||||
from arrival to departure, integrating the city guide
|
||||
information with practical travel logistics.
|
||||
|
||||
Your final answer MUST be a complete expanded travel plan,
|
||||
formatted as markdown, encompassing a daily schedule,
|
||||
anticipated weather conditions, recommended clothing and
|
||||
items to pack, and a detailed budget, ensuring THE BEST
|
||||
TRIP EVER. Be specific and give it a reason why you picked
|
||||
each place, what makes them special!
|
||||
|
||||
Trip Date: {range}
|
||||
Traveling from: {origin}
|
||||
Traveler Interests: {interests}
|
||||
""",
|
||||
)
|
||||
|
||||
root_agent = Agent(
|
||||
model='gemini-2.0-flash-001',
|
||||
name='trip_planner',
|
||||
description='Plan the best trip ever',
|
||||
instruction="""
|
||||
Your goal is to plan the best trip according to information listed above.
|
||||
You describe why did you choose the city, list top 3
|
||||
attactions and provide a detailed itinerary for each day.""",
|
||||
sub_agents=[identify_agent, gather_agent, plan_agent],
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "test_id",
|
||||
"app_name": "trip_planner_agent",
|
||||
"user_id": "test_user",
|
||||
"state": {
|
||||
"origin": "San Francisco",
|
||||
"interests": "Food, Shopping, Museums",
|
||||
"range": "1000 miles",
|
||||
"cities": ""
|
||||
},
|
||||
"events": [],
|
||||
"last_update_time": 1741218714.258285
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"criteria": {
|
||||
"response_match_score": 0.5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "test_id",
|
||||
"app_name": "trip_planner_agent",
|
||||
"user_id": "test_user",
|
||||
"state": {
|
||||
"origin": "San Francisco",
|
||||
"interests": "Food, Shopping, Museums",
|
||||
"range": "1000 miles",
|
||||
"cities": ""
|
||||
},
|
||||
"events": [],
|
||||
"last_update_time": 1741218714.258285
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"criteria": {
|
||||
"response_match_score": 0.5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"query": "Based on my interests, where should I go, Yosemite national park or Los Angeles?",
|
||||
"expected_tool_use": [],
|
||||
"reference": "Given your interests in food, shopping, and museums, Los Angeles would be a better choice than Yosemite National Park. Yosemite is primarily focused on outdoor activities and natural landscapes, while Los Angeles offers a diverse range of culinary experiences, shopping districts, and world-class museums. I will now gather information to create an in-depth guide for your trip to Los Angeles.\n"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
[
|
||||
{
|
||||
"query": "Hi, who are you? What can you do?",
|
||||
"expected_tool_use": [],
|
||||
"reference": "I am trip_planner, and my goal is to plan the best trip ever. I can describe why a city was chosen, list its top attractions, and provide a detailed itinerary for each day of the trip.\n"
|
||||
},
|
||||
{
|
||||
"query": "I want to travel from San Francisco to an European country in fall next year. I am considering London and Paris. What is your advice?",
|
||||
"expected_tool_use": [
|
||||
{
|
||||
"tool_name": "transfer_to_agent",
|
||||
"tool_input": {
|
||||
"agent_name": "indentify_agent"
|
||||
}
|
||||
}
|
||||
],
|
||||
"reference": "Okay, I can help you analyze London and Paris to determine which city is better for your trip next fall. I will consider weather patterns, seasonal events, travel costs (including flights from San Francisco), and your interests (food, shopping, and museums). After gathering this information, I'll provide a detailed report on my chosen city.\n"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user