Python: Introducing AI Function approval (#1131)

* support for local function approval

* small fix

* fix mypy

* added bigger test scenario's for function calling and approvals

* updated lock

* updated return message for rejection

* fix test

* updated function result content handling
This commit is contained in:
Eduard van Valkenburg
2025-10-04 15:19:16 +00:00
committed by GitHub
parent 01f438d710
commit fd819c6c02
18 changed files with 1535 additions and 304 deletions
@@ -34,6 +34,7 @@ from agent_framework import (
UsageContent,
UsageDetails,
get_logger,
prepare_function_call_results,
use_chat_middleware,
use_function_invocation,
)
@@ -84,7 +85,7 @@ from azure.ai.projects.aio import AIProjectClient
from azure.ai.projects.models import ConnectionType
from azure.core.credentials_async import AsyncTokenCredential
from azure.core.exceptions import HttpResponseError, ResourceNotFoundError
from pydantic import BaseModel, ValidationError
from pydantic import ValidationError
if sys.version_info >= (3, 11):
from typing import Self # pragma: no cover
@@ -897,23 +898,9 @@ class AzureAIAgentClient(BaseChatClient):
if isinstance(content, FunctionResultContent):
if tool_outputs is None:
tool_outputs = []
result_contents: list[Any] = (
content.result if isinstance(content.result, list) else [content.result]
tool_outputs.append(
ToolOutput(tool_call_id=call_id, output=prepare_function_call_results(content.result))
)
results: list[Any] = []
for item in result_contents:
if isinstance(item, Contents):
results.append(
json.dumps(item.to_dict(exclude={"raw_representation", "additional_properties"}))
)
elif isinstance(item, BaseModel):
results.append(item.model_dump_json())
else:
results.append(json.dumps(item))
if len(results) == 1:
tool_outputs.append(ToolOutput(tool_call_id=call_id, output=results[0]))
else:
tool_outputs.append(ToolOutput(tool_call_id=call_id, output=json.dumps(results)))
elif isinstance(content, FunctionApprovalResponseContent):
if tool_approvals is None:
tool_approvals = []
@@ -31,6 +31,7 @@ from agent_framework import (
TextContent,
UriContent,
)
from agent_framework._serialization import SerializationMixin
from agent_framework.exceptions import ServiceInitializationError
from azure.ai.agents.models import (
CodeInterpreterToolDefinition,
@@ -1123,7 +1124,7 @@ async def test_azure_ai_chat_client_convert_required_action_to_tool_output_funct
assert tool_outputs is not None
assert len(tool_outputs) == 1
assert tool_outputs[0].tool_call_id == "call_456"
assert tool_outputs[0].output == '"Simple result"'
assert tool_outputs[0].output == "Simple result"
async def test_azure_ai_chat_client_convert_required_action_invalid_call_id(mock_ai_project_client: MagicMock) -> None:
@@ -1155,14 +1156,15 @@ async def test_azure_ai_chat_client_convert_required_action_invalid_structure(
assert tool_approvals is None
async def test_azure_ai_chat_client_convert_required_action_basemodel_results(
async def test_azure_ai_chat_client_convert_required_action_serde_model_results(
mock_ai_project_client: MagicMock,
) -> None:
"""Test _convert_required_action_to_tool_output with BaseModel results."""
class MockResult(BaseModel):
name: str
value: int
class MockResult(SerializationMixin):
def __init__(self, name: str, value: int):
self.name = name
self.value = value
chat_client = create_test_azure_ai_chat_client(mock_ai_project_client, agent_id="test-agent")
@@ -1178,7 +1180,7 @@ async def test_azure_ai_chat_client_convert_required_action_basemodel_results(
assert len(tool_outputs) == 1
assert tool_outputs[0].tool_call_id == "call_456"
# Should use model_dump_json for BaseModel
expected_json = mock_result.model_dump_json()
expected_json = mock_result.to_json()
assert tool_outputs[0].output == expected_json
@@ -1187,8 +1189,9 @@ async def test_azure_ai_chat_client_convert_required_action_multiple_results(
) -> None:
"""Test _convert_required_action_to_tool_output with multiple results."""
class MockResult(BaseModel):
data: str
class MockResult(SerializationMixin):
def __init__(self, data: str):
self.data = data
chat_client = create_test_azure_ai_chat_client(mock_ai_project_client, agent_id="test-agent")
@@ -1206,9 +1209,9 @@ async def test_azure_ai_chat_client_convert_required_action_multiple_results(
# Should JSON dump the entire results array since len > 1
expected_results = [
mock_basemodel.model_dump_json(), # BaseModel uses model_dump_json
json.dumps({"key": "value"}), # Dict uses json.dumps
json.dumps("string_result"), # String uses json.dumps
mock_basemodel.to_dict(),
{"key": "value"},
"string_result",
]
expected_output = json.dumps(expected_results)
assert tool_outputs[0].output == expected_output