mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
fix minor typos
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
# Copy as .env file and fill your values below to run integration tests.
|
||||
|
||||
# Choose Backend: GOOGLE_AI_ONLY | VERTEX_ONLY | BOTH (default)
|
||||
TEST_BACKEND=BOTH
|
||||
|
||||
# ML Dev backend config
|
||||
GOOGLE_API_KEY=YOUR_VALUE_HERE
|
||||
# Vertex backend config
|
||||
GOOGLE_CLOUD_PROJECT=YOUR_VALUE_HERE
|
||||
GOOGLE_CLOUD_LOCATION=YOUR_VALUE_HERE
|
||||
@@ -0,0 +1,18 @@
|
||||
# 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 pytest
|
||||
|
||||
# This allows pytest to show the values of the asserts.
|
||||
pytest.register_assert_rewrite('tests.integration.utils')
|
||||
@@ -0,0 +1,119 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Literal
|
||||
import warnings
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from google.adk import Agent
|
||||
from pytest import fixture
|
||||
from pytest import FixtureRequest
|
||||
from pytest import hookimpl
|
||||
from pytest import Metafunc
|
||||
|
||||
from .utils import TestRunner
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_env_for_tests():
|
||||
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
|
||||
if not os.path.exists(dotenv_path):
|
||||
warnings.warn(
|
||||
f'Missing .env file at {dotenv_path}. See dotenv.sample for an example.'
|
||||
)
|
||||
else:
|
||||
load_dotenv(dotenv_path, override=True, verbose=True)
|
||||
if 'GOOGLE_API_KEY' not in os.environ:
|
||||
warnings.warn(
|
||||
'Missing GOOGLE_API_KEY in the environment variables. GOOGLE_AI backend'
|
||||
' integration tests will fail.'
|
||||
)
|
||||
for env_var in [
|
||||
'GOOGLE_CLOUD_PROJECT',
|
||||
'GOOGLE_CLOUD_LOCATION',
|
||||
]:
|
||||
if env_var not in os.environ:
|
||||
warnings.warn(
|
||||
f'Missing {env_var} in the environment variables. Vertex backend'
|
||||
' integration tests will fail.'
|
||||
)
|
||||
|
||||
|
||||
load_env_for_tests()
|
||||
|
||||
BackendType = Literal['GOOGLE_AI', 'VERTEX']
|
||||
|
||||
|
||||
@fixture
|
||||
def agent_runner(request: FixtureRequest) -> TestRunner:
|
||||
assert isinstance(request.param, dict)
|
||||
|
||||
if 'agent' in request.param:
|
||||
assert isinstance(request.param['agent'], Agent)
|
||||
return TestRunner(request.param['agent'])
|
||||
elif 'agent_name' in request.param:
|
||||
assert isinstance(request.param['agent_name'], str)
|
||||
return TestRunner.from_agent_name(request.param['agent_name'])
|
||||
|
||||
raise NotImplementedError('Must provide agent or agent_name.')
|
||||
|
||||
|
||||
@fixture(autouse=True)
|
||||
def llm_backend(request: FixtureRequest):
|
||||
# Set backend environment value.
|
||||
original_val = os.environ.get('GOOGLE_GENAI_USE_VERTEXAI')
|
||||
backend_type = request.param
|
||||
if backend_type == 'GOOGLE_AI':
|
||||
os.environ['GOOGLE_GENAI_USE_VERTEXAI'] = '0'
|
||||
else:
|
||||
os.environ['GOOGLE_GENAI_USE_VERTEXAI'] = '1'
|
||||
|
||||
yield # Run the test
|
||||
|
||||
# Restore the environment
|
||||
if original_val is None:
|
||||
os.environ.pop('GOOGLE_GENAI_USE_VERTEXAI', None)
|
||||
else:
|
||||
os.environ['GOOGLE_GENAI_USE_VERTEXAI'] = original_val
|
||||
|
||||
|
||||
@hookimpl(tryfirst=True)
|
||||
def pytest_generate_tests(metafunc: Metafunc):
|
||||
if llm_backend.__name__ in metafunc.fixturenames:
|
||||
if not _is_explicitly_marked(llm_backend.__name__, metafunc):
|
||||
test_backend = os.environ.get('TEST_BACKEND', 'BOTH')
|
||||
if test_backend == 'GOOGLE_AI_ONLY':
|
||||
metafunc.parametrize(llm_backend.__name__, ['GOOGLE_AI'], indirect=True)
|
||||
elif test_backend == 'VERTEX_ONLY':
|
||||
metafunc.parametrize(llm_backend.__name__, ['VERTEX'], indirect=True)
|
||||
elif test_backend == 'BOTH':
|
||||
metafunc.parametrize(
|
||||
llm_backend.__name__, ['GOOGLE_AI', 'VERTEX'], indirect=True
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f'Invalid TEST_BACKEND value: {test_backend}, should be one of'
|
||||
' [GOOGLE_AI_ONLY, VERTEX_ONLY, BOTH]'
|
||||
)
|
||||
|
||||
|
||||
def _is_explicitly_marked(mark_name: str, metafunc: Metafunc) -> bool:
|
||||
if hasattr(metafunc.function, 'pytestmark'):
|
||||
for mark in metafunc.function.pytestmark:
|
||||
if mark.name == 'parametrize' and mark.args[0] == mark_name:
|
||||
return True
|
||||
return False
|
||||
@@ -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.
|
||||
|
||||
@@ -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,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"}
|
||||
),
|
||||
)
|
||||
@@ -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,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,
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
gkcng
|
||||
@@ -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,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],
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,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,
|
||||
],
|
||||
)
|
||||
@@ -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,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
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user