Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 12 additions & 17 deletions src/google/adk/cli/conformance/cli_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,23 +72,18 @@ async def _create_conformance_test_files(
# long-running tool. Replace the function call ID with the actual
# function call ID. This is needed because the function call ID is not
# known when writing the test case.
if (
user_message.content.parts
and user_message.content.parts[0].function_response
and user_message.content.parts[0].function_response.name
):
if (
user_message.content.parts[0].function_response.name
not in function_call_name_to_id_map
):
raise ValueError(
"Function response for"
f" {user_message.content.parts[0].function_response.name} does"
" not match any pending function call."
)
content.parts[0].function_response.id = function_call_name_to_id_map[
user_message.content.parts[0].function_response.name
]
if user_message.content.parts:
for part in content.parts:
if part.function_response and part.function_response.name:
if part.function_response.name not in function_call_name_to_id_map:
raise ValueError(
"Function response for"
f" {part.function_response.name} does"
" not match any pending function call."
)
part.function_response.id = function_call_name_to_id_map[
part.function_response.name
]
elif user_message.text is not None:
content = types.UserContent(parts=[types.Part(text=user_message.text)])
else:
Expand Down
29 changes: 12 additions & 17 deletions src/google/adk/cli/conformance/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,23 +142,18 @@ async def _run_user_messages(
# long-running tool. Replace the function call ID with the actual
# function call ID. This is needed because the function call ID is not
# known when writing the test case.
if (
user_message.content.parts
and user_message.content.parts[0].function_response
and user_message.content.parts[0].function_response.name
):
if (
user_message.content.parts[0].function_response.name
not in function_call_name_to_id_map
):
raise ValueError(
"Function response for"
f" {user_message.content.parts[0].function_response.name} does"
" not match any pending function call."
)
content.parts[0].function_response.id = function_call_name_to_id_map[
user_message.content.parts[0].function_response.name
]
if user_message.content.parts:
for part in content.parts:
if part.function_response and part.function_response.name:
if part.function_response.name not in function_call_name_to_id_map:
raise ValueError(
"Function response for"
f" {part.function_response.name} does"
" not match any pending function call."
)
part.function_response.id = function_call_name_to_id_map[
part.function_response.name
]
elif user_message.text is not None:
content = types.UserContent(parts=[types.Part(text=user_message.text)])
else:
Expand Down
7 changes: 5 additions & 2 deletions src/google/adk/telemetry/tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,11 @@ def trace_tool_call(
and function_response_event.content is not None
and function_response_event.content.parts
):
response_parts = function_response_event.content.parts
function_response = response_parts[0].function_response
function_response = None
for part in function_response_event.content.parts:
if part.function_response and part.function_response.name == tool.name:
function_response = part.function_response
break
if function_response is not None:
if function_response.id is not None:
tool_call_id = function_response.id
Expand Down
79 changes: 40 additions & 39 deletions src/google/adk/tools/load_artifacts_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,46 +286,47 @@ async def _append_artifacts_to_llm_request(
# Attach the content of the artifacts if the model requests them.
# This only adds the content to the model request, instead of the session.
if llm_request.contents and llm_request.contents[-1].parts:
function_response = llm_request.contents[-1].parts[0].function_response
if function_response and function_response.name == 'load_artifacts':
response = function_response.response or {}
artifact_names = response.get('artifact_names', [])
for artifact_name in artifact_names:
# Try session-scoped first (default behavior)
artifact = await tool_context.load_artifact(artifact_name)

# If not found and name doesn't already have user: prefix,
# try cross-session artifacts with user: prefix
if artifact is None and not artifact_name.startswith('user:'):
prefixed_name = f'user:{artifact_name}'
artifact = await tool_context.load_artifact(prefixed_name)

if artifact is None:
logger.warning('Artifact "%s" not found, skipping', artifact_name)
continue

artifact_part = _as_safe_part_for_llm(artifact, artifact_name)
if artifact_part is not artifact:
mime_type = (
artifact.inline_data.mime_type if artifact.inline_data else None
)
logger.debug(
'Converted artifact "%s" (mime_type=%s) to text Part',
artifact_name,
mime_type,
)

llm_request.contents.append(
types.Content(
role='user',
parts=[
types.Part.from_text(
text=f'Artifact {artifact_name} is:'
),
artifact_part,
],
for part in llm_request.contents[-1].parts:
function_response = part.function_response
if function_response and function_response.name == 'load_artifacts':
response = function_response.response or {}
artifact_names = response.get('artifact_names', [])
for artifact_name in artifact_names:
# Try session-scoped first (default behavior)
artifact = await tool_context.load_artifact(artifact_name)

# If not found and name doesn't already have user: prefix,
# try cross-session artifacts with user: prefix
if artifact is None and not artifact_name.startswith('user:'):
prefixed_name = f'user:{artifact_name}'
artifact = await tool_context.load_artifact(prefixed_name)

if artifact is None:
logger.warning('Artifact "%s" not found, skipping', artifact_name)
continue

artifact_part = _as_safe_part_for_llm(artifact, artifact_name)
if artifact_part is not artifact:
mime_type = (
artifact.inline_data.mime_type if artifact.inline_data else None
)
logger.debug(
'Converted artifact "%s" (mime_type=%s) to text Part',
artifact_name,
mime_type,
)
)

llm_request.contents.append(
types.Content(
role='user',
parts=[
types.Part.from_text(
text=f'Artifact {artifact_name} is:'
),
artifact_part,
],
)
)


load_artifacts_tool = LoadArtifactsTool()
51 changes: 26 additions & 25 deletions src/google/adk/tools/load_mcp_resource_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,32 +124,33 @@ async def _append_resources_to_llm_request(

# Attach content
if llm_request.contents and llm_request.contents[-1].parts:
function_response = llm_request.contents[-1].parts[0].function_response
if function_response and function_response.name == self.name:
response = function_response.response or {}
resource_names = response.get("resource_names", [])
for resource_name in resource_names:
try:
contents = await self._mcp_toolset.read_resource(resource_name)

for content in contents:
part = self._mcp_content_to_part(content, resource_name)
llm_request.contents.append(
types.Content(
role="user",
parts=[
types.Part.from_text(
text=f"Resource {resource_name} is:"
),
part,
],
)
for part in llm_request.contents[-1].parts:
function_response = part.function_response
if function_response and function_response.name == self.name:
response = function_response.response or {}
resource_names = response.get("resource_names", [])
for resource_name in resource_names:
try:
contents = await self._mcp_toolset.read_resource(resource_name)

for content in contents:
part = self._mcp_content_to_part(content, resource_name)
llm_request.contents.append(
types.Content(
role="user",
parts=[
types.Part.from_text(
text=f"Resource {resource_name} is:"
),
part,
],
)
)
except Exception as e:
logger.warning(
"Failed to read MCP resource '%s': %s", resource_name, e
)
except Exception as e:
logger.warning(
"Failed to read MCP resource '%s': %s", resource_name, e
)
continue
continue

def _mcp_content_to_part(
self, content: Any, resource_name: str
Expand Down
4 changes: 2 additions & 2 deletions tests/unittests/telemetry/test_spans.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ def test_trace_tool_call_with_scalar_response(
types.Part(
function_response=types.FunctionResponse(
id=test_tool_call_id,
name='test_function_1',
name=mock_tool_fixture.name,
response={'result': scalar_function_response},
)
),
Expand Down Expand Up @@ -602,7 +602,7 @@ def test_trace_tool_call_with_dict_response(
types.Part(
function_response=types.FunctionResponse(
id=test_tool_call_id,
name='test_function_1',
name=mock_tool_fixture.name,
response=dict_function_response,
)
),
Expand Down