mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
82ca4065cb
* test with stack and simplified names * quick demo of agent decorator * moved builder to protocol to enhance functionality * undid chatclientAgent -> agent rename * one more * reverted AIAgent rename * final reverts * fixed foundry import * revert changes * streamlined otel and fcc decorators * cleanup of telemetry * further refinement * lots of updates * fixed typing * fix for mypy * added input and output atttributes * fix import * initial work on baking in otel * major update to telemetry * final fixes after rename * fix * fix test * updated tests * fix for tests * fixes for tests * updated based on comments * removed agent decorator * fix for Python: ServiceResponseException when using multiple tools Fixes #649 * addressed comments * fix tests * fix tests * fix tools tests * fix for conversation_id in assistants client * fix responses test * fix tests and mypy * updated test * foundry fix --------- Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
206 lines
7.6 KiB
Python
206 lines
7.6 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
import sys
|
|
from collections.abc import Sequence
|
|
from typing import Any
|
|
|
|
from pytest import fixture
|
|
|
|
from agent_framework import (
|
|
BaseChatClient,
|
|
ChatClientProtocol,
|
|
ChatMessage,
|
|
ChatResponse,
|
|
ChatResponseUpdate,
|
|
EmbeddingGenerator,
|
|
FunctionCallContent,
|
|
FunctionResultContent,
|
|
GeneratedEmbeddings,
|
|
Role,
|
|
TextContent,
|
|
ai_function,
|
|
)
|
|
|
|
if sys.version_info >= (3, 12):
|
|
pass # type: ignore
|
|
else:
|
|
pass # type: ignore[import]
|
|
|
|
|
|
class MockEmbeddingGenerator:
|
|
"""Simple implementation of an embedding generator."""
|
|
|
|
async def generate(
|
|
self,
|
|
input_data: Sequence[str],
|
|
**kwargs: Any,
|
|
) -> GeneratedEmbeddings[list[float]]:
|
|
# Implement the method
|
|
embeddings = GeneratedEmbeddings[list[float]]()
|
|
for i, _ in enumerate(input_data):
|
|
embeddings.append([0.0 * 1, 0.1 * 1, 0.2 * 1, 0.3 * i, 0.4 * i])
|
|
return embeddings
|
|
|
|
|
|
@fixture
|
|
def embedding_generator() -> MockEmbeddingGenerator:
|
|
gen: EmbeddingGenerator[str, list[float]] = MockEmbeddingGenerator()
|
|
return gen
|
|
|
|
|
|
def test_chat_client_type(chat_client: ChatClientProtocol):
|
|
assert isinstance(chat_client, ChatClientProtocol)
|
|
|
|
|
|
async def test_chat_client_get_response(chat_client: ChatClientProtocol):
|
|
response = await chat_client.get_response(ChatMessage(role="user", text="Hello"))
|
|
assert response.text == "test response"
|
|
assert response.messages[0].role == Role.ASSISTANT
|
|
|
|
|
|
async def test_chat_client_get_streaming_response(chat_client: ChatClientProtocol):
|
|
async for update in chat_client.get_streaming_response(ChatMessage(role="user", text="Hello")):
|
|
assert update.text == "test streaming response " or update.text == "another update"
|
|
assert update.role == Role.ASSISTANT
|
|
|
|
|
|
def test_embedding_generator_type(embedding_generator: MockEmbeddingGenerator):
|
|
assert isinstance(embedding_generator, EmbeddingGenerator)
|
|
|
|
|
|
async def test_embedding_generator_generate(embedding_generator: MockEmbeddingGenerator):
|
|
input_data = ["Hello", "world"]
|
|
embeddings = await embedding_generator.generate(input_data)
|
|
assert len(embeddings) == len(input_data)
|
|
for emb in embeddings:
|
|
assert len(emb) == 5
|
|
|
|
|
|
def test_base_client(chat_client_base: ChatClientProtocol):
|
|
assert isinstance(chat_client_base, BaseChatClient)
|
|
assert isinstance(chat_client_base, ChatClientProtocol)
|
|
|
|
|
|
async def test_base_client_get_response(chat_client_base: ChatClientProtocol):
|
|
response = await chat_client_base.get_response(ChatMessage(role="user", text="Hello"))
|
|
assert response.messages[0].role == Role.ASSISTANT
|
|
assert response.messages[0].text == "test response - Hello"
|
|
|
|
|
|
async def test_base_client_get_streaming_response(chat_client_base: ChatClientProtocol):
|
|
async for update in chat_client_base.get_streaming_response(ChatMessage(role="user", text="Hello")):
|
|
assert update.text == "update - Hello" or update.text == "another update"
|
|
|
|
|
|
async def test_base_client_with_function_calling(chat_client_base: ChatClientProtocol):
|
|
exec_counter = 0
|
|
|
|
@ai_function(name="test_function")
|
|
def ai_func(arg1: str) -> str:
|
|
nonlocal exec_counter
|
|
exec_counter += 1
|
|
return f"Processed {arg1}"
|
|
|
|
chat_client_base.run_responses = [
|
|
ChatResponse(
|
|
messages=ChatMessage(
|
|
role="assistant",
|
|
contents=[FunctionCallContent(call_id="1", name="test_function", arguments='{"arg1": "value1"}')],
|
|
)
|
|
),
|
|
ChatResponse(messages=ChatMessage(role="assistant", text="done")),
|
|
]
|
|
response = await chat_client_base.get_response("hello", tool_choice="auto", tools=[ai_func])
|
|
assert exec_counter == 1
|
|
assert len(response.messages) == 3
|
|
assert response.messages[0].role == Role.ASSISTANT
|
|
assert isinstance(response.messages[0].contents[0], FunctionCallContent)
|
|
assert response.messages[0].contents[0].name == "test_function"
|
|
assert response.messages[0].contents[0].arguments == '{"arg1": "value1"}'
|
|
assert response.messages[0].contents[0].call_id == "1"
|
|
assert response.messages[1].role == Role.TOOL
|
|
assert isinstance(response.messages[1].contents[0], FunctionResultContent)
|
|
assert response.messages[1].contents[0].call_id == "1"
|
|
assert response.messages[1].contents[0].result == "Processed value1"
|
|
assert response.messages[2].role == Role.ASSISTANT
|
|
assert response.messages[2].text == "done"
|
|
|
|
|
|
async def test_base_client_with_function_calling_resets(chat_client_base: ChatClientProtocol):
|
|
exec_counter = 0
|
|
|
|
@ai_function(name="test_function")
|
|
def ai_func(arg1: str) -> str:
|
|
nonlocal exec_counter
|
|
exec_counter += 1
|
|
return f"Processed {arg1}"
|
|
|
|
chat_client_base.run_responses = [
|
|
ChatResponse(
|
|
messages=ChatMessage(
|
|
role="assistant",
|
|
contents=[FunctionCallContent(call_id="1", name="test_function", arguments='{"arg1": "value1"}')],
|
|
)
|
|
),
|
|
ChatResponse(
|
|
messages=ChatMessage(
|
|
role="assistant",
|
|
contents=[FunctionCallContent(call_id="2", name="test_function", arguments='{"arg1": "value1"}')],
|
|
)
|
|
),
|
|
ChatResponse(messages=ChatMessage(role="assistant", text="done")),
|
|
]
|
|
response = await chat_client_base.get_response("hello", tool_choice="auto", tools=[ai_func])
|
|
assert exec_counter == 2
|
|
assert len(response.messages) == 5
|
|
assert response.messages[0].role == Role.ASSISTANT
|
|
assert response.messages[1].role == Role.TOOL
|
|
assert response.messages[2].role == Role.ASSISTANT
|
|
assert response.messages[3].role == Role.TOOL
|
|
assert response.messages[4].role == Role.ASSISTANT
|
|
assert isinstance(response.messages[0].contents[0], FunctionCallContent)
|
|
assert isinstance(response.messages[1].contents[0], FunctionResultContent)
|
|
assert isinstance(response.messages[2].contents[0], FunctionCallContent)
|
|
assert isinstance(response.messages[3].contents[0], FunctionResultContent)
|
|
# after these two responses, it would try another regular call, but since max_iterations is 1, it stops and calls
|
|
assert isinstance(response.messages[4].contents[0], TextContent)
|
|
assert response.text == "I broke out of the function invocation loop..."
|
|
|
|
|
|
async def test_base_client_with_streaming_function_calling(chat_client_base: ChatClientProtocol):
|
|
exec_counter = 0
|
|
|
|
@ai_function(name="test_function")
|
|
def ai_func(arg1: str) -> str:
|
|
nonlocal exec_counter
|
|
exec_counter += 1
|
|
return f"Processed {arg1}"
|
|
|
|
chat_client_base.streaming_responses = [
|
|
[
|
|
ChatResponseUpdate(
|
|
contents=[FunctionCallContent(call_id="1", name="test_function", arguments='{"arg1":')],
|
|
role="assistant",
|
|
),
|
|
ChatResponseUpdate(
|
|
contents=[FunctionCallContent(call_id="1", name="test_function", arguments='"value1"}')],
|
|
role="assistant",
|
|
),
|
|
],
|
|
[
|
|
ChatResponseUpdate(
|
|
contents=[TextContent(text="Processed value1")],
|
|
role="assistant",
|
|
)
|
|
],
|
|
]
|
|
updates = []
|
|
async for update in chat_client_base.get_streaming_response("hello", tool_choice="auto", tools=[ai_func]):
|
|
updates.append(update)
|
|
assert len(updates) == 4 # two updates with the function call, the function result and the final text
|
|
assert updates[0].contents[0].call_id == "1"
|
|
assert updates[1].contents[0].call_id == "1"
|
|
assert updates[2].contents[0].call_id == "1"
|
|
assert updates[3].text == "Processed value1"
|
|
assert exec_counter == 1
|