Skip to content

Commit d4f6703

Browse files
authored
Python: fix Bing grounding & custom search content generation (#12760)
### Motivation and Context The same `agent_content_generation` method is used to handle both `bing_grounding` and `bing_custom_search` tool calls; however, the method expected that the dicts on the underlying types were named the same - which they aren't. They have their respective names, as shown above. <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description Fix Bing content generation based on the instance we're dealing with. - Add unit tests. - Closes #12746 <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [X] The code builds clean without any errors or warnings - [X] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [X] All unit tests pass, and I have added new tests where possible - [X] I didn't break anyone 😄
1 parent b723fbd commit d4f6703

File tree

3 files changed

+87
-15
lines changed

3 files changed

+87
-15
lines changed

python/semantic_kernel/agents/azure_ai/agent_content_generation.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
RequiredMcpToolCall,
1919
RunStep,
2020
RunStepAzureAISearchToolCall,
21+
RunStepBingCustomSearchToolCall,
2122
RunStepBingGroundingToolCall,
2223
RunStepDeltaCodeInterpreterImageOutput,
2324
RunStepDeltaCodeInterpreterLogOutput,
@@ -283,16 +284,26 @@ def generate_function_result_content(
283284

284285
@experimental
285286
def generate_bing_grounding_content(
286-
agent_name: str, bing_tool_call: "RunStepBingGroundingToolCall"
287+
agent_name: str, bing_tool_call: "RunStepBingGroundingToolCall | RunStepBingCustomSearchToolCall"
287288
) -> ChatMessageContent:
288-
"""Generate function result content related to a Bing Grounding Tool."""
289+
"""Generate function result content related to a Bing Grounding Tool or Bing Custom Search Tool."""
289290
message_content: ChatMessageContent = ChatMessageContent(role=AuthorRole.ASSISTANT, name=agent_name) # type: ignore
291+
292+
# Extract tool details based on the specific tool type
293+
if isinstance(bing_tool_call, RunStepBingGroundingToolCall):
294+
tool_details = bing_tool_call.bing_grounding
295+
elif isinstance(bing_tool_call, RunStepBingCustomSearchToolCall):
296+
tool_details = bing_tool_call.bing_custom_search
297+
else:
298+
# This should never happen with proper typing, but provides safety
299+
raise TypeError(f"Unsupported Bing tool call type: {type(bing_tool_call)}")
300+
290301
message_content.items.append(
291302
FunctionCallContent(
292303
id=bing_tool_call.id,
293304
name=bing_tool_call.type,
294305
function_name=bing_tool_call.type,
295-
arguments=bing_tool_call.bing_grounding,
306+
arguments=tool_details,
296307
)
297308
)
298309
return message_content
@@ -464,21 +475,27 @@ def generate_streaming_function_content(
464475
def generate_streaming_bing_grounding_content(
465476
agent_name: str, step_details: "RunStepDeltaToolCallObject"
466477
) -> StreamingChatMessageContent | None:
467-
"""Generate StreamingChatMessageContent for Bing Grounding tool calls, filtering out empty results."""
478+
"""Generate StreamingChatMessageContent for Bing Grounding and Bing Custom Search tool calls."""
468479
if not step_details.tool_calls:
469480
return None
470481

471482
items: list[FunctionCallContent] = []
472483
for index, tool in enumerate(step_details.tool_calls):
473-
if tool.type != "bing_grounding":
484+
if tool.type not in ("bing_grounding", "bing_custom_search"):
474485
continue
475486

476-
arguments = tool.get("bing_grounding", {}) or {}
477-
requesturl = arguments.get("requesturl", "")
478-
response_metadata = arguments.get("response_metadata", None)
487+
# Extract tool details based on the specific tool type
488+
if tool.type == "bing_grounding":
489+
tool_details = tool.get("bing_grounding", {})
490+
elif tool.type == "bing_custom_search":
491+
tool_details = tool.get("bing_custom_search", {})
492+
else:
493+
continue
494+
495+
request_url = tool_details.get("requesturl", None)
496+
response_metadata = tool_details.get("response_metadata", None)
479497

480-
# Only skip if BOTH are missing/empty
481-
if requesturl == "" and response_metadata is None:
498+
if not request_url and not response_metadata:
482499
continue
483500

484501
items.append(
@@ -487,7 +504,7 @@ def generate_streaming_bing_grounding_content(
487504
index=index,
488505
name=tool.type,
489506
function_name=tool.type,
490-
arguments=arguments,
507+
arguments=tool_details,
491508
)
492509
)
493510

python/semantic_kernel/agents/azure_ai/agent_thread_actions.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
ResponseFormatJsonSchemaType,
1717
RunStep,
1818
RunStepAzureAISearchToolCall,
19+
RunStepBingCustomSearchToolCall,
1920
RunStepBingGroundingToolCall,
2021
RunStepCodeInterpreterToolCall,
2122
RunStepDeltaChunk,
@@ -340,11 +341,12 @@ def sort_key(step: RunStep):
340341
| AgentsNamedToolChoiceType.BING_CUSTOM_SEARCH
341342
):
342343
logger.debug(
343-
f"Entering tool_calls (bing_grounding) for run [{run.id}], agent "
344-
f" `{agent.name}` and thread `{thread_id}`"
344+
f"Entering tool_calls (bing_grounding/bing_custom_search) for run [{run.id}], "
345+
f"agent `{agent.name}` and thread `{thread_id}`"
345346
)
346-
bing_call: RunStepBingGroundingToolCall = cast(
347-
RunStepBingGroundingToolCall, tool_call
347+
# Handle both Bing grounding and custom search tool calls
348+
bing_call: RunStepBingGroundingToolCall | RunStepBingCustomSearchToolCall = cast(
349+
RunStepBingGroundingToolCall | RunStepBingCustomSearchToolCall, tool_call
348350
)
349351
content = generate_bing_grounding_content(
350352
agent_name=agent.name, bing_tool_call=bing_call

python/tests/unit/agents/azure_ai_agent/test_agent_content_generation.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
MessageTextUrlCitationDetails,
2929
RequiredFunctionToolCall,
3030
RunStep,
31+
RunStepBingCustomSearchToolCall,
32+
RunStepBingGroundingToolCall,
3133
RunStepDeltaFunction,
3234
RunStepDeltaFunctionToolCall,
3335
RunStepDeltaToolCallObject,
@@ -38,6 +40,7 @@
3840

3941
from semantic_kernel.agents.azure_ai.agent_content_generation import (
4042
generate_annotation_content,
43+
generate_bing_grounding_content,
4144
generate_code_interpreter_content,
4245
generate_function_call_content,
4346
generate_function_result_content,
@@ -407,3 +410,53 @@ def test_generate_code_interpreter_content():
407410
def test_generate_streaming_code_interpreter_content_no_calls():
408411
step_details = type("Details", (), {"tool_calls": None})
409412
assert generate_streaming_code_interpreter_content("my_agent", step_details) is None
413+
414+
415+
def test_generate_bing_grounding_content():
416+
"""Test generate_bing_grounding_content with RunStepBingGroundingToolCall."""
417+
bing_grounding_tool_call = RunStepBingGroundingToolCall(
418+
id="call_gvgTmSL4hgdxWP4O7LLnwMlt",
419+
bing_grounding={
420+
"requesturl": "https://api.bing.microsoft.com/v7.0/search?q=search",
421+
"response_metadata": "{'market': 'en-US', 'num_docs_retrieved': 5, 'num_docs_actually_used': 5}",
422+
},
423+
)
424+
425+
msg = generate_bing_grounding_content("my_agent", bing_grounding_tool_call)
426+
427+
assert len(msg.items) == 1
428+
assert msg.role == AuthorRole.ASSISTANT
429+
assert isinstance(msg.items[0], FunctionCallContent)
430+
assert msg.items[0].id == "call_gvgTmSL4hgdxWP4O7LLnwMlt"
431+
assert msg.items[0].name == "bing_grounding"
432+
assert msg.items[0].function_name == "bing_grounding"
433+
assert msg.items[0].arguments["requesturl"] == "https://api.bing.microsoft.com/v7.0/search?q=search"
434+
assert msg.items[0].arguments["response_metadata"] == (
435+
"{'market': 'en-US', 'num_docs_retrieved': 5, 'num_docs_actually_used': 5}"
436+
)
437+
438+
439+
def test_generate_bing_custom_search_content():
440+
"""Test generate_bing_grounding_content with RunStepBingCustomSearchToolCall."""
441+
bing_custom_search_tool_call = RunStepBingCustomSearchToolCall(
442+
id="call_abc123def456ghi",
443+
bing_custom_search={
444+
"query": "semantic kernel python",
445+
"custom_config_id": "config_123",
446+
"search_results": "{'num_results': 10, 'top_result': 'Microsoft Semantic Kernel'}",
447+
},
448+
)
449+
450+
msg = generate_bing_grounding_content("my_agent", bing_custom_search_tool_call)
451+
452+
assert len(msg.items) == 1
453+
assert msg.role == AuthorRole.ASSISTANT
454+
assert isinstance(msg.items[0], FunctionCallContent)
455+
assert msg.items[0].id == "call_abc123def456ghi"
456+
assert msg.items[0].name == "bing_custom_search"
457+
assert msg.items[0].function_name == "bing_custom_search"
458+
assert msg.items[0].arguments["query"] == "semantic kernel python"
459+
assert msg.items[0].arguments["custom_config_id"] == "config_123"
460+
assert msg.items[0].arguments["search_results"] == (
461+
"{'num_results': 10, 'top_result': 'Microsoft Semantic Kernel'}"
462+
)

0 commit comments

Comments
 (0)