ag2 0.9.10__py3-none-any.whl → 0.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ag2 might be problematic. Click here for more details.

Files changed (42) hide show
  1. {ag2-0.9.10.dist-info → ag2-0.10.0.dist-info}/METADATA +14 -7
  2. {ag2-0.9.10.dist-info → ag2-0.10.0.dist-info}/RECORD +42 -24
  3. autogen/a2a/__init__.py +36 -0
  4. autogen/a2a/agent_executor.py +105 -0
  5. autogen/a2a/client.py +280 -0
  6. autogen/a2a/errors.py +18 -0
  7. autogen/a2a/httpx_client_factory.py +79 -0
  8. autogen/a2a/server.py +221 -0
  9. autogen/a2a/utils.py +165 -0
  10. autogen/agentchat/__init__.py +3 -0
  11. autogen/agentchat/agent.py +0 -2
  12. autogen/agentchat/chat.py +5 -1
  13. autogen/agentchat/contrib/llava_agent.py +1 -13
  14. autogen/agentchat/conversable_agent.py +178 -73
  15. autogen/agentchat/group/group_tool_executor.py +46 -15
  16. autogen/agentchat/group/guardrails.py +41 -33
  17. autogen/agentchat/group/multi_agent_chat.py +53 -0
  18. autogen/agentchat/group/safeguards/api.py +19 -2
  19. autogen/agentchat/group/safeguards/enforcer.py +134 -40
  20. autogen/agentchat/groupchat.py +45 -33
  21. autogen/agentchat/realtime/experimental/realtime_swarm.py +1 -3
  22. autogen/interop/pydantic_ai/pydantic_ai.py +1 -1
  23. autogen/llm_config/client.py +3 -2
  24. autogen/oai/bedrock.py +0 -13
  25. autogen/oai/client.py +15 -8
  26. autogen/oai/client_utils.py +30 -0
  27. autogen/oai/cohere.py +0 -10
  28. autogen/remote/__init__.py +18 -0
  29. autogen/remote/agent.py +199 -0
  30. autogen/remote/agent_service.py +142 -0
  31. autogen/remote/errors.py +17 -0
  32. autogen/remote/httpx_client_factory.py +131 -0
  33. autogen/remote/protocol.py +37 -0
  34. autogen/remote/retry.py +102 -0
  35. autogen/remote/runtime.py +96 -0
  36. autogen/testing/__init__.py +12 -0
  37. autogen/testing/messages.py +45 -0
  38. autogen/testing/test_agent.py +111 -0
  39. autogen/version.py +1 -1
  40. {ag2-0.9.10.dist-info → ag2-0.10.0.dist-info}/WHEEL +0 -0
  41. {ag2-0.9.10.dist-info → ag2-0.10.0.dist-info}/licenses/LICENSE +0 -0
  42. {ag2-0.9.10.dist-info → ag2-0.10.0.dist-info}/licenses/NOTICE.md +0 -0
@@ -0,0 +1,142 @@
1
+ # Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ import warnings
6
+ from typing import Any, Literal, cast
7
+
8
+ from autogen.agentchat import ConversableAgent
9
+ from autogen.agentchat.conversable_agent import normilize_message_to_oai
10
+ from autogen.agentchat.group.context_variables import ContextVariables
11
+ from autogen.agentchat.group.group_tool_executor import GroupToolExecutor
12
+ from autogen.agentchat.group.reply_result import ReplyResult
13
+ from autogen.agentchat.group.targets.transition_target import TransitionTarget
14
+
15
+ from .protocol import RemoteService, RequestMessage, ResponseMessage, get_tool_names
16
+
17
+
18
+ class AgentService(RemoteService):
19
+ def __init__(self, agent: ConversableAgent) -> None:
20
+ self.name = agent.name
21
+ self.agent = agent
22
+
23
+ async def __call__(self, state: RequestMessage) -> ResponseMessage | None:
24
+ out_message: dict[str, Any] | None
25
+ if guardrail_result := self.agent.run_input_guardrails(state.messages):
26
+ # input guardrail activated by initial messages
27
+ _, out_message = normilize_message_to_oai(guardrail_result.reply, self.agent.name, role="assistant")
28
+ return ResponseMessage(messages=[out_message], context=state.context)
29
+
30
+ context_variables = ContextVariables(state.context)
31
+ tool_executor = self._make_tool_executor(context_variables)
32
+
33
+ local_history: list[dict[str, Any]] = []
34
+ while True:
35
+ messages = state.messages + local_history
36
+
37
+ # TODO: catch ask user input event
38
+ is_final, _ = await self.agent.a_check_termination_and_human_reply(messages)
39
+ if is_final:
40
+ break
41
+
42
+ reply = await self.agent.a_generate_reply(
43
+ messages,
44
+ exclude=(
45
+ ConversableAgent.check_termination_and_human_reply,
46
+ ConversableAgent.a_check_termination_and_human_reply,
47
+ ConversableAgent.generate_oai_reply,
48
+ ConversableAgent.a_generate_oai_reply,
49
+ ),
50
+ )
51
+
52
+ if not reply:
53
+ _, reply = await self.agent.a_generate_oai_reply(
54
+ messages,
55
+ tools=state.client_tools,
56
+ )
57
+
58
+ should_continue, out_message = self._add_message_to_local_history(reply, role="assistant")
59
+ if out_message:
60
+ local_history.append(out_message)
61
+ if not should_continue:
62
+ break
63
+ out_message = cast(dict[str, Any], out_message)
64
+
65
+ called_tools = get_tool_names(out_message.get("tool_calls", []))
66
+ if state.client_tool_names.intersection(called_tools):
67
+ break # return client tool execution command back to client
68
+
69
+ tool_result, updated_context_variables = self._try_execute_local_tool(tool_executor, out_message)
70
+
71
+ if updated_context_variables:
72
+ context_variables.update(updated_context_variables.to_dict())
73
+
74
+ should_continue, out_message = self._add_message_to_local_history(tool_result, role="tool")
75
+ if out_message:
76
+ local_history.append(out_message)
77
+ if not should_continue:
78
+ break
79
+
80
+ if not local_history:
81
+ return None
82
+
83
+ return ResponseMessage(messages=local_history, context=context_variables.data or None)
84
+
85
+ def _add_message_to_local_history(
86
+ self, message: str | dict[str, Any] | None, role: str
87
+ ) -> tuple[Literal[True], dict[str, Any]] | tuple[Literal[False], dict[str, Any] | None]:
88
+ if message is None:
89
+ return False, None # output message is empty, interrupt the loop
90
+
91
+ if guardrail_result := self.agent.run_output_guardrails(message):
92
+ _, out_message = normilize_message_to_oai(guardrail_result.reply, self.agent.name, role=role)
93
+ return False, out_message # output guardrail activated, interrupt the loop
94
+
95
+ valid, out_message = normilize_message_to_oai(message, self.agent.name, role=role)
96
+ if not valid:
97
+ return False, None # tool result is not valid OAI message, interrupt the loop
98
+
99
+ return True, out_message
100
+
101
+ def _make_tool_executor(self, context_variables: ContextVariables) -> GroupToolExecutor:
102
+ tool_executor = GroupToolExecutor()
103
+ for tool in self.agent.tools:
104
+ # TODO: inject ChatContext to tool
105
+ new_tool = tool_executor.make_tool_copy_with_context_variables(tool, context_variables) or tool
106
+ tool_executor.register_for_execution(serialize=False, silent_override=True)(new_tool)
107
+ return tool_executor
108
+
109
+ def _try_execute_local_tool(
110
+ self,
111
+ tool_executor: GroupToolExecutor,
112
+ tool_message: dict[str, Any],
113
+ ) -> tuple[dict[str, Any] | None, ContextVariables | None]:
114
+ tool_result: dict[str, Any] | None = None
115
+ updated_context_variables: ContextVariables | None = None
116
+
117
+ if "tool_calls" in tool_message:
118
+ _, tool_result = tool_executor.generate_tool_calls_reply([tool_message])
119
+ if tool_result is None:
120
+ return tool_result, updated_context_variables
121
+
122
+ if "tool_responses" in tool_result:
123
+ # TODO: catch handoffs
124
+ for tool_response in tool_result["tool_responses"]:
125
+ content = tool_response["content"]
126
+
127
+ if isinstance(content, TransitionTarget):
128
+ warnings.warn(
129
+ f"Tool {self.agent.name} returned a target, which is not supported in remote mode"
130
+ )
131
+
132
+ elif isinstance(content, ReplyResult):
133
+ if content.target:
134
+ warnings.warn(
135
+ f"Tool {self.agent.name} returned a target, which is not supported in remote mode"
136
+ )
137
+
138
+ if content.context_variables:
139
+ updated_context_variables = content.context_variables
140
+ tool_response["content"] = content.message
141
+
142
+ return tool_result, updated_context_variables
@@ -0,0 +1,17 @@
1
+ # Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+
6
+ class RemoteAgentError(Exception):
7
+ """Base class for remote agent errors"""
8
+
9
+ pass
10
+
11
+
12
+ class RemoteAgentNotFoundError(RemoteAgentError):
13
+ """Raised when a remote agent is not found"""
14
+
15
+ def __init__(self, agent_name: str) -> None:
16
+ self.agent_name = agent_name
17
+ super().__init__(f"Remote agent `{agent_name}` not found")
@@ -0,0 +1,131 @@
1
+ # Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ import ssl
6
+ import typing
7
+ from typing import Protocol
8
+
9
+ from httpx._client import AsyncClient, Client, EventHook
10
+ from httpx._config import DEFAULT_LIMITS, DEFAULT_MAX_REDIRECTS, DEFAULT_TIMEOUT_CONFIG, Limits
11
+ from httpx._transports.base import AsyncBaseTransport
12
+ from httpx._types import AuthTypes, CertTypes, CookieTypes, HeaderTypes, ProxyTypes, QueryParamTypes, TimeoutTypes
13
+ from httpx._urls import URL
14
+
15
+ from autogen.doc_utils import export_module
16
+
17
+
18
+ class ClientFactory(Protocol):
19
+ def __call__(self) -> AsyncClient: ...
20
+
21
+ def make_sync(self) -> Client: ...
22
+
23
+
24
+ @export_module("autogen.a2a")
25
+ class HttpxClientFactory(ClientFactory):
26
+ """
27
+ An asynchronous HTTP client factory, with connection pooling, HTTP/2, redirects,
28
+ cookie persistence, etc.
29
+
30
+ It can be shared between tasks.
31
+
32
+ Usage:
33
+
34
+ ```python
35
+ >>> factory = HttpxClientFactory()
36
+ >>> async with factory() as client:
37
+ >>> response = await client.get('https://example.org')
38
+ ```
39
+
40
+ **Parameters:**
41
+
42
+ * **auth** - *(optional)* An authentication class to use when sending
43
+ requests.
44
+ * **params** - *(optional)* Query parameters to include in request URLs, as
45
+ a string, dictionary, or sequence of two-tuples.
46
+ * **headers** - *(optional)* Dictionary of HTTP headers to include when
47
+ sending requests.
48
+ * **cookies** - *(optional)* Dictionary of Cookie items to include when
49
+ sending requests.
50
+ * **verify** - *(optional)* Either `True` to use an SSL context with the
51
+ default CA bundle, `False` to disable verification, or an instance of
52
+ `ssl.SSLContext` to use a custom context.
53
+ * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be
54
+ enabled. Defaults to `False`.
55
+ * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
56
+ * **timeout** - *(optional)* The timeout configuration to use when sending
57
+ requests.
58
+ * **limits** - *(optional)* The limits configuration to use.
59
+ * **max_redirects** - *(optional)* The maximum number of redirect responses
60
+ that should be followed.
61
+ * **base_url** - *(optional)* A URL to use as the base when building
62
+ request URLs.
63
+ * **transport** - *(optional)* A transport class to use for sending requests
64
+ over the network.
65
+ * **trust_env** - *(optional)* Enables or disables usage of environment
66
+ variables for configuration.
67
+ * **default_encoding** - *(optional)* The default encoding to use for decoding
68
+ response text, if no charset information is included in a response Content-Type
69
+ header. Set to a callable for automatic character set detection. Default: "utf-8".
70
+ """
71
+
72
+ def __init__(
73
+ self,
74
+ *,
75
+ auth: AuthTypes | None = None,
76
+ params: QueryParamTypes | None = None,
77
+ headers: HeaderTypes | None = None,
78
+ cookies: CookieTypes | None = None,
79
+ verify: ssl.SSLContext | str | bool = True,
80
+ cert: CertTypes | None = None,
81
+ http1: bool = True,
82
+ http2: bool = False,
83
+ proxy: ProxyTypes | None = None,
84
+ mounts: None | (typing.Mapping[str, AsyncBaseTransport | None]) = None,
85
+ timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
86
+ follow_redirects: bool = False,
87
+ limits: Limits = DEFAULT_LIMITS,
88
+ max_redirects: int = DEFAULT_MAX_REDIRECTS,
89
+ event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
90
+ base_url: URL | str = "",
91
+ transport: AsyncBaseTransport | None = None,
92
+ trust_env: bool = True,
93
+ default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
94
+ **kwargs: typing.Any,
95
+ ) -> None:
96
+ self.options = {
97
+ "auth": auth,
98
+ "params": params,
99
+ "headers": headers,
100
+ "cookies": cookies,
101
+ "verify": verify,
102
+ "cert": cert,
103
+ "http1": http1,
104
+ "http2": http2,
105
+ "proxy": proxy,
106
+ "mounts": mounts,
107
+ "timeout": timeout,
108
+ "follow_redirects": follow_redirects,
109
+ "limits": limits,
110
+ "max_redirects": max_redirects,
111
+ "event_hooks": event_hooks,
112
+ "base_url": base_url,
113
+ "transport": transport,
114
+ "trust_env": trust_env,
115
+ "default_encoding": default_encoding,
116
+ **kwargs,
117
+ }
118
+
119
+ def __call__(self) -> AsyncClient:
120
+ return AsyncClient(**self.options)
121
+
122
+ def make_sync(self) -> Client:
123
+ return Client(**self.options)
124
+
125
+
126
+ class EmptyClientFactory(ClientFactory):
127
+ def __call__(self) -> AsyncClient:
128
+ return AsyncClient(timeout=30.0)
129
+
130
+ def make_sync(self) -> Client:
131
+ return Client(timeout=30.0)
@@ -0,0 +1,37 @@
1
+ # Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ from typing import Any, Protocol
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class AgentBusMessage(BaseModel):
10
+ messages: list[dict[str, Any]]
11
+ context: dict[str, Any] | None = None
12
+
13
+
14
+ class RequestMessage(AgentBusMessage):
15
+ client_tools: list[dict[str, Any]] = Field(default_factory=list)
16
+
17
+ @property
18
+ def client_tool_names(self) -> set[str]:
19
+ return get_tool_names(self.client_tools)
20
+
21
+
22
+ class ResponseMessage(AgentBusMessage):
23
+ pass
24
+
25
+
26
+ class RemoteService(Protocol):
27
+ """Interface to make AgentBus compatible with non AG2 systems."""
28
+
29
+ name: str
30
+
31
+ async def __call__(self, state: RequestMessage) -> ResponseMessage | None:
32
+ """Executable that consumes Conversation State and returns a new state."""
33
+ ...
34
+
35
+
36
+ def get_tool_names(tools: list[dict[str, Any]]) -> set[str]:
37
+ return set(filter(bool, (tool.get("function", {}).get("name", "") for tool in tools)))
@@ -0,0 +1,102 @@
1
+ # Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ import time
5
+ from types import TracebackType
6
+ from typing import Protocol
7
+
8
+ import anyio
9
+
10
+
11
+ class RetryPolicyManager(Protocol):
12
+ def __enter__(self) -> None:
13
+ pass
14
+
15
+ async def __aenter__(self) -> None:
16
+ pass
17
+
18
+ def __exit__(
19
+ self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
20
+ ) -> None | bool:
21
+ pass
22
+
23
+ async def __aexit__(
24
+ self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
25
+ ) -> None | bool:
26
+ pass
27
+
28
+
29
+ class RetryPolicy(Protocol):
30
+ def __call__(self) -> RetryPolicyManager: ...
31
+
32
+
33
+ class SleepRetryPolicy(RetryPolicy):
34
+ def __init__(self, retry_interval: float = 10.0, retry_count: int = 3) -> None:
35
+ self.retry_interval = retry_interval
36
+ self.retry_count = retry_count
37
+
38
+ def __call__(self) -> RetryPolicyManager:
39
+ return _SleepRetryPolicy(self.retry_interval, self.retry_count)
40
+
41
+
42
+ class _SleepRetryPolicy(RetryPolicyManager):
43
+ def __init__(self, retry_interval: float = 10.0, retry_count: int = 3) -> None:
44
+ self.retry_interval = retry_interval
45
+ self.retry_count = retry_count
46
+ self.errors_count = 0
47
+
48
+ def __enter__(self) -> None:
49
+ pass
50
+
51
+ async def __aenter__(self) -> None:
52
+ pass
53
+
54
+ def __exit__(
55
+ self,
56
+ exc_type: type[BaseException] | None,
57
+ exc_value: BaseException | None,
58
+ traceback: TracebackType | None,
59
+ ) -> None | bool:
60
+ if exc_type is not None:
61
+ self.errors_count += 1
62
+ should_suppress = self.errors_count < self.retry_count
63
+ time.sleep(self.retry_interval)
64
+ return should_suppress
65
+ return None
66
+
67
+ async def __aexit__(
68
+ self,
69
+ exc_type: type[BaseException] | None,
70
+ exc_value: BaseException | None,
71
+ traceback: TracebackType | None,
72
+ ) -> None | bool:
73
+ if exc_type is not None:
74
+ self.errors_count += 1
75
+ should_suppress = self.errors_count < self.retry_count
76
+ await anyio.sleep(self.retry_interval)
77
+ return should_suppress
78
+ return None
79
+
80
+
81
+ class NoRetryPolicy(RetryPolicyManager):
82
+ def __enter__(self) -> None:
83
+ pass
84
+
85
+ async def __aenter__(self) -> None:
86
+ pass
87
+
88
+ async def __aexit__(
89
+ self,
90
+ exc_type: type[BaseException] | None,
91
+ exc_value: BaseException | None,
92
+ traceback: TracebackType | None,
93
+ ) -> None | bool:
94
+ pass
95
+
96
+ def __exit__(
97
+ self,
98
+ exc_type: type[BaseException] | None,
99
+ exc_value: BaseException | None,
100
+ traceback: TracebackType | None,
101
+ ) -> None | bool:
102
+ pass
@@ -0,0 +1,96 @@
1
+ # Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ import asyncio
6
+ from collections.abc import Awaitable, Callable, Iterable, MutableMapping
7
+ from itertools import chain
8
+ from typing import Any
9
+ from uuid import UUID, uuid4
10
+
11
+ from fastapi import FastAPI, HTTPException, Response, status
12
+
13
+ from autogen.agentchat import ConversableAgent
14
+
15
+ from .agent_service import AgentService
16
+ from .protocol import RemoteService, RequestMessage, ResponseMessage
17
+
18
+
19
+ class HTTPAgentBus:
20
+ def __init__(
21
+ self,
22
+ agents: Iterable[ConversableAgent] = (),
23
+ *,
24
+ long_polling_interval: float = 10.0,
25
+ additional_services: Iterable[RemoteService] = (),
26
+ ) -> None:
27
+ """Create HTTPAgentBus runtime.
28
+
29
+ Makes the passed agents capable of processing remote calls.
30
+
31
+ Args:
32
+ agents: Agents to register as remote services.
33
+ long_polling_interval: Timeout to respond on task status calls for long-living executions.
34
+ Should be less than clients' HTTP request timeout.
35
+ additional_services: Additional services to register.
36
+ """
37
+ self.app = FastAPI()
38
+
39
+ for service in chain(map(AgentService, agents), additional_services):
40
+ register_agent_endpoints(
41
+ app=self.app,
42
+ service=service,
43
+ long_polling_interval=long_polling_interval,
44
+ )
45
+
46
+ async def __call__(
47
+ self,
48
+ scope: MutableMapping[str, Any],
49
+ receive: Callable[[], Awaitable[MutableMapping[str, Any]]],
50
+ send: Callable[[MutableMapping[str, Any]], Awaitable[None]],
51
+ ) -> None:
52
+ """ASGI interface."""
53
+ await self.app(scope, receive, send)
54
+
55
+
56
+ def register_agent_endpoints(
57
+ app: FastAPI,
58
+ service: RemoteService,
59
+ long_polling_interval: float,
60
+ ) -> None:
61
+ tasks: dict[UUID, asyncio.Task[ResponseMessage | None]] = {}
62
+
63
+ @app.get(f"/{service.name}" + "/{task_id}", response_model=ResponseMessage | None)
64
+ async def remote_call_result(task_id: UUID) -> Response | ResponseMessage | None:
65
+ if task_id not in tasks:
66
+ raise HTTPException(
67
+ detail=f"`{task_id}` task not found",
68
+ status_code=status.HTTP_404_NOT_FOUND,
69
+ )
70
+
71
+ task = tasks[task_id]
72
+
73
+ await asyncio.wait(
74
+ (task, asyncio.create_task(asyncio.sleep(long_polling_interval))),
75
+ return_when=asyncio.FIRST_COMPLETED,
76
+ )
77
+
78
+ if not task.done():
79
+ return Response(status_code=status.HTTP_425_TOO_EARLY)
80
+
81
+ try:
82
+ reply = task.result() # Task inner errors raising here
83
+ finally:
84
+ # TODO: how to clear hanged tasks?
85
+ tasks.pop(task_id, None)
86
+
87
+ if reply is None:
88
+ return Response(status_code=status.HTTP_204_NO_CONTENT)
89
+
90
+ return reply
91
+
92
+ @app.post(f"/{service.name}", status_code=status.HTTP_202_ACCEPTED)
93
+ async def remote_call_starter(state: RequestMessage) -> UUID:
94
+ task, task_id = asyncio.create_task(service(state)), uuid4()
95
+ tasks[task_id] = task
96
+ return task_id
@@ -0,0 +1,12 @@
1
+ # Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from .messages import ToolCall, tools_message
6
+ from .test_agent import TestAgent
7
+
8
+ __all__ = (
9
+ "TestAgent",
10
+ "ToolCall",
11
+ "tools_message",
12
+ )
@@ -0,0 +1,45 @@
1
+ # Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from typing import Any
6
+ from uuid import uuid4
7
+
8
+ from pydantic_core import to_json
9
+
10
+ from autogen.events.agent_events import FunctionCall
11
+ from autogen.events.agent_events import ToolCall as RawToolCall
12
+
13
+
14
+ class ToolCall:
15
+ """Represents a tool call with a specified tool name and arguments.
16
+
17
+ Args:
18
+ tool_name: Tool name to call. Tool should be rigestered in Agent you send message.
19
+ arguments: keyword arguments to pass to the tool.
20
+ """
21
+
22
+ def __init__(self, tool_name: str, /, **arguments: Any) -> None:
23
+ self.tool_message = RawToolCall(
24
+ id=f"call_{uuid4()}",
25
+ type="function",
26
+ function=FunctionCall(name=tool_name, arguments=to_json(arguments).decode()),
27
+ ).model_dump()
28
+
29
+ def to_message(self) -> dict[str, Any]:
30
+ """Convert the tool call to a message format suitable for API calls.
31
+
32
+ Returns:
33
+ A dictionary containing the tool call in message format,
34
+ ready to be used in API requests or message queues.
35
+ """
36
+ return tools_message(self)
37
+
38
+
39
+ def tools_message(*tool_calls: ToolCall) -> dict[str, Any]:
40
+ """Convert multiple tool calls into a message format suitable for API calls.
41
+
42
+ Args:
43
+ *tool_calls: One or more ToolCall objects to convert.
44
+ """
45
+ return {"content": None, "tool_calls": [c.tool_message for c in tool_calls]}