Python: simple Agent sample (#180)

* tweaks to agents and sample

* updated clients and agents

* single line run and print

* improved tool handling

* added note on setting max iterations

* fixed streaming param name

* updated tools test

* made kwargs alphabetical

* added params to run methods

* tweak to ensure right overload
This commit is contained in:
Eduard van Valkenburg
2025-07-15 16:01:21 +02:00
committed by GitHub
Unverified
parent 59bf7343af
commit 62917ee5e5
8 changed files with 773 additions and 194 deletions
@@ -0,0 +1,38 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import Any
from pydantic import BaseModel
from pytest import fixture
from agent_framework import AITool, ai_function
@fixture
def ai_tool() -> AITool:
"""Returns a generic AITool."""
class GenericTool(BaseModel):
name: str
description: str | None = None
additional_properties: dict[str, Any] | None = None
def parameters(self) -> dict[str, Any]:
"""Return the parameters of the tool as a JSON schema."""
return {
"name": {"type": "string"},
}
return GenericTool(name="generic_tool", description="A generic tool")
@fixture
def ai_function_tool() -> AITool:
"""Returns a executable AITool."""
@ai_function
def simple_function(x: int, y: int) -> int:
"""A simple function that adds two numbers."""
return x + y
return simple_function
@@ -209,7 +209,7 @@ async def test_base_client_with_function_calling(chat_client_base: MockChatClien
async def test_base_client_with_function_calling_disabled(chat_client_base: MockChatClientBase):
chat_client_base.maximum_iterations_per_request = 0
chat_client_base.__maximum_iterations_per_request = 0
exec_counter = 0
@ai_function(name="test_function")
@@ -273,7 +273,7 @@ async def test_base_client_with_streaming_function_calling(chat_client_base: Moc
async def test_base_client_with_streaming_function_calling_disabled(chat_client_base: MockChatClientBase):
chat_client_base.maximum_iterations_per_request = 0
chat_client_base.__maximum_iterations_per_request = 0
exec_counter = 0
@ai_function(name="test_function")
+96 -1
View File
@@ -10,7 +10,9 @@ from agent_framework import (
AgentRunResponseUpdate,
AIContent,
AIContents,
AITool,
ChatMessage,
ChatOptions,
ChatResponse,
ChatResponseUpdate,
ChatRole,
@@ -492,6 +494,99 @@ def test_generated_embeddings():
assert issubclass(GeneratedEmbeddings, MutableSequence)
# region: ChatOptions
def test_chat_options_init() -> None:
options = ChatOptions()
assert options.ai_model_id is None
def test_chat_options_init_with_args(ai_function_tool, ai_tool) -> None:
options = ChatOptions(
ai_model_id="gpt-4",
max_tokens=1024,
temperature=0.7,
top_p=0.9,
presence_penalty=0.0,
frequency_penalty=0.0,
user="user-123",
tools=[ai_function_tool, ai_tool],
)
assert options.ai_model_id == "gpt-4"
assert options.max_tokens == 1024
assert options.temperature == 0.7
assert options.top_p == 0.9
assert options.presence_penalty == 0.0
assert options.frequency_penalty == 0.0
assert options.user == "user-123"
for tool in options._ai_tools:
assert isinstance(tool, AITool)
assert tool.name is not None
assert tool.description is not None
assert tool.parameters() is not None
def test_chat_options_and(ai_function_tool, ai_tool) -> None:
options1 = ChatOptions(ai_model_id="gpt-4o", tools=[ai_function_tool])
options2 = ChatOptions(ai_model_id="gpt-4.1", tools=[ai_tool])
assert options1 != options2
options3 = options1 & options2
assert options3.ai_model_id == "gpt-4.1"
assert len(options3._ai_tools) == 2
assert options3._ai_tools == [ai_function_tool, ai_tool]
assert options3.tools == [ai_function_tool, ai_tool]
def test_chat_options_parsing_tools(ai_function_tool, ai_tool) -> None:
from agent_framework._clients import _prepare_tools_and_tool_choice
def echo() -> str:
"""Echo the input."""
return "Echo"
dict_function = {
"type": "function",
"function": {
"name": "get_weather",
"description": "Retrieves current weather for the given location.",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City and country e.g. Bogotá, Colombia"},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Units the temperature will be returned in.",
},
},
"required": ["location", "units"],
"additionalProperties": False,
},
"strict": True,
},
}
options = ChatOptions(tools=[ai_function_tool, ai_tool, echo, dict_function], tool_choice="auto")
assert len(options.tools) == 4
assert options.tools[0] == ai_function_tool
assert options.tools[1] == ai_tool
assert options.tools[2] != echo
assert options.tools[3] == dict_function
# after prepare, the tools should be represented as dicts
# while ai_tools is still the same.
_prepare_tools_and_tool_choice(options)
assert options._ai_tools[0] == ai_function_tool
assert options._ai_tools[1] == ai_tool
assert options._ai_tools[3] == dict_function
assert len(options.tools) == 4
assert options.tools[0]["function"]["name"] == "simple_function"
assert options.tools[1]["function"]["name"] == "generic_tool"
assert options.tools[2]["function"]["name"] == "echo"
assert options.tools[3]["function"]["name"] == "get_weather"
# region Agent Response Fixtures
@@ -548,7 +643,7 @@ def test_agent_run_response_from_updates(agent_run_response_update: AgentRunResp
updates = [agent_run_response_update, agent_run_response_update]
response = AgentRunResponse.from_agent_run_response_updates(updates)
assert len(response.messages) > 0
assert response.text == "Test content\nTest content"
assert response.text == "Test content Test content"
def test_agent_run_response_str_method(chat_message: ChatMessage) -> None: