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
Original file line number Diff line number Diff line change
Expand Up @@ -1551,6 +1551,7 @@ async def _execute_tool_call(
FunctionExecutionResult(
content=f"Error: {e}",
call_id=tool_call.id,
uuid=tool_call.uuid,
is_error=True,
name=tool_call.name,
),
Expand All @@ -1568,6 +1569,7 @@ async def _execute_tool_call(
FunctionExecutionResult(
content=result_as_str,
call_id=tool_call.id,
uuid=tool_call.uuid,
is_error=False,
name=tool_call.name,
),
Expand Down Expand Up @@ -1608,6 +1610,7 @@ async def _execute_tool_call(
FunctionExecutionResult(
content=tool_result.to_text(),
call_id=tool_call.id,
uuid=tool_call.uuid,
is_error=tool_result.is_error,
name=tool_call.name,
),
Expand All @@ -1618,6 +1621,7 @@ async def _execute_tool_call(
FunctionExecutionResult(
content=f"Error: tool '{tool_call.name}' not found in any workbench",
call_id=tool_call.id,
uuid=tool_call.uuid,
is_error=True,
name=tool_call.name,
),
Expand Down
15 changes: 7 additions & 8 deletions python/packages/autogen-agentchat/tests/test_assistant_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,15 +237,14 @@ async def test_model_client_stream_with_tool_calls() -> None:
assert isinstance(message.messages[-1], TextMessage)
assert isinstance(message.messages[1], ToolCallRequestEvent)
assert message.messages[-1].content == "Example response 2 to task"
assert message.messages[1].content == [
FunctionCall(id="1", name="_pass_function", arguments=r'{"input": "task"}'),
FunctionCall(id="3", name="_echo_function", arguments=r'{"input": "task"}'),
]
tool_calls = message.messages[1].content
assert [call.id for call in tool_calls] == ["1", "3"]
assert [call.name for call in tool_calls] == ["_pass_function", "_echo_function"]
assert isinstance(message.messages[2], ToolCallExecutionEvent)
assert message.messages[2].content == [
FunctionExecutionResult(call_id="1", content="pass", is_error=False, name="_pass_function"),
FunctionExecutionResult(call_id="3", content="task", is_error=False, name="_echo_function"),
]
tool_results = message.messages[2].content
assert [result.call_id for result in tool_results] == ["1", "3"]
assert [result.content for result in tool_results] == ["pass", "task"]
assert [result.uuid for result in tool_results] == [call.uuid for call in tool_calls]
elif isinstance(message, ModelClientStreamingChunkEvent):
chunks.append(message.content)
assert "".join(chunks) == "Example response 2 to task"
Expand Down
5 changes: 4 additions & 1 deletion python/packages/autogen-core/src/autogen_core/_types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from dataclasses import dataclass
import uuid
from dataclasses import dataclass, field


@dataclass
Expand All @@ -10,3 +11,5 @@ class FunctionCall:
arguments: str
# Function to call
name: str
# Framework-generated identifier for correlating the call with its result.
uuid: str = field(default_factory=lambda: str(uuid.uuid4()), compare=False)
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ class FunctionExecutionResult(BaseModel):
call_id: str
"""The ID of the function call. Note this ID may be empty for some models."""

uuid: str | None = None
"""Framework-generated identifier for correlating this result with its function call."""

is_error: bool | None = None
"""Whether the function call resulted in an error."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ async def tool_agent_caller_loop(
elif isinstance(result, ToolException):
function_results.append(
FunctionExecutionResult(
content=f"Error: {result}", call_id=result.call_id, is_error=True, name=result.name
content=f"Error: {result}",
call_id=result.call_id,
is_error=True,
name=result.name,
uuid=result.uuid,
)
)
elif isinstance(result, BaseException):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import List
from uuid import uuid4

from .. import FunctionCall, MessageContext, RoutedAgent, message_handler
from ..models import FunctionExecutionResult
Expand All @@ -20,6 +21,7 @@ class ToolException(BaseException):
call_id: str
content: str
name: str
uuid: str = field(default_factory=lambda: str(uuid4()))


@dataclass
Expand Down Expand Up @@ -78,7 +80,10 @@ async def handle_function_call(self, message: FunctionCall, ctx: MessageContext)
tool = next((tool for tool in self._tools if tool.name == message.name), None)
if tool is None:
raise ToolNotFoundException(
call_id=message.id, content=f"Error: Tool not found: {message.name}", name=message.name
call_id=message.id,
content=f"Error: Tool not found: {message.name}",
name=message.name,
uuid=message.uuid,
)
else:
try:
Expand All @@ -89,8 +94,15 @@ async def handle_function_call(self, message: FunctionCall, ctx: MessageContext)
result_as_str = tool.return_value_as_string(result)
except json.JSONDecodeError as e:
raise InvalidToolArgumentsException(
call_id=message.id, content=f"Error: Invalid arguments: {message.arguments}", name=message.name
call_id=message.id,
content=f"Error: Invalid arguments: {message.arguments}",
name=message.name,
uuid=message.uuid,
) from e
except Exception as e:
raise ToolExecutionException(call_id=message.id, content=f"Error: {e}", name=message.name) from e
return FunctionExecutionResult(content=result_as_str, call_id=message.id, is_error=False, name=message.name)
raise ToolExecutionException(
call_id=message.id, content=f"Error: {e}", name=message.name, uuid=message.uuid
) from e
return FunctionExecutionResult(
content=result_as_str, call_id=message.id, is_error=False, name=message.name, uuid=message.uuid
)
17 changes: 13 additions & 4 deletions python/packages/autogen-core/tests/test_tool_agent.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import json
import logging
import uuid
from typing import Any, AsyncGenerator, List, Literal, Mapping, Optional, Sequence, Union

import pytest
Expand Down Expand Up @@ -62,10 +63,9 @@ async def test_tool_agent(caplog: pytest.LogCaptureFixture) -> None:
runtime.start()

# Test pass function
result = await runtime.send_message(
FunctionCall(id="1", arguments=json.dumps({"input": "pass"}), name="pass"), agent
)
assert result == FunctionExecutionResult(call_id="1", content="pass", is_error=False, name="pass")
call = FunctionCall(id="1", arguments=json.dumps({"input": "pass"}), name="pass")
result = await runtime.send_message(call, agent)
assert result == FunctionExecutionResult(call_id="1", content="pass", is_error=False, name="pass", uuid=call.uuid)

# Check log.
assert any(("ToolCall" in record.message and str(agent) in record.message) for record in caplog.records)
Expand Down Expand Up @@ -94,6 +94,15 @@ async def test_tool_agent(caplog: pytest.LogCaptureFixture) -> None:
await runtime.stop()


def test_function_call_has_unique_uuid() -> None:
first = FunctionCall(id="1", arguments="{}", name="test")
second = FunctionCall(id="1", arguments="{}", name="test")

assert first.uuid != second.uuid
uuid.UUID(first.uuid)
uuid.UUID(second.uuid)


@pytest.mark.asyncio
async def test_caller_loop() -> None:
class MockChatCompletionClient(ChatCompletionClient):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,11 @@ async def on_messages_stream(
is_error = True
tool_outputs.append(
FunctionExecutionResult(
content=result, call_id=tool_call.id, is_error=is_error, name=tool_call.name
content=result,
call_id=tool_call.id,
uuid=tool_call.uuid,
is_error=is_error,
name=tool_call.name,
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,11 @@ async def on_messages_stream(
is_error = True
tool_outputs.append(
FunctionExecutionResult(
content=result, call_id=tool_call.id, is_error=is_error, name=tool_call.name
content=result,
call_id=tool_call.id,
uuid=tool_call.uuid,
is_error=is_error,
name=tool_call.name,
)
)

Expand Down