ag2 0.3.2b2__py3-none-any.whl → 0.4__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.
- {ag2-0.3.2b2.dist-info → ag2-0.4.dist-info}/METADATA +17 -10
- {ag2-0.3.2b2.dist-info → ag2-0.4.dist-info}/RECORD +17 -14
- autogen/agentchat/__init__.py +18 -2
- autogen/agentchat/contrib/agent_builder.py +7 -5
- autogen/agentchat/contrib/captainagent.py +487 -0
- autogen/agentchat/contrib/llamaindex_conversable_agent.py +9 -0
- autogen/agentchat/contrib/swarm_agent.py +414 -0
- autogen/agentchat/contrib/tool_retriever.py +114 -0
- autogen/agentchat/conversable_agent.py +11 -7
- autogen/agentchat/groupchat.py +2 -0
- autogen/oai/completion.py +2 -2
- autogen/oai/gemini.py +1 -1
- autogen/version.py +1 -1
- {ag2-0.3.2b2.dist-info → ag2-0.4.dist-info}/LICENSE +0 -0
- {ag2-0.3.2b2.dist-info → ag2-0.4.dist-info}/NOTICE.md +0 -0
- {ag2-0.3.2b2.dist-info → ag2-0.4.dist-info}/WHEEL +0 -0
- {ag2-0.3.2b2.dist-info → ag2-0.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# Copyright (c) 2023 - 2024, Owners of https://github.com/ag2ai
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import json
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from inspect import signature
|
|
8
|
+
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from autogen.function_utils import get_function_schema
|
|
13
|
+
from autogen.oai import OpenAIWrapper
|
|
14
|
+
|
|
15
|
+
from ..agent import Agent
|
|
16
|
+
from ..chat import ChatResult
|
|
17
|
+
from ..conversable_agent import ConversableAgent
|
|
18
|
+
from ..groupchat import GroupChat, GroupChatManager
|
|
19
|
+
from ..user_proxy_agent import UserProxyAgent
|
|
20
|
+
|
|
21
|
+
# Parameter name for context variables
|
|
22
|
+
# Use the value in functions and they will be substituted with the context variables:
|
|
23
|
+
# e.g. def my_function(context_variables: Dict[str, Any], my_other_parameters: Any) -> Any:
|
|
24
|
+
__CONTEXT_VARIABLES_PARAM_NAME__ = "context_variables"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AfterWorkOption(Enum):
|
|
28
|
+
TERMINATE = "TERMINATE"
|
|
29
|
+
REVERT_TO_USER = "REVERT_TO_USER"
|
|
30
|
+
STAY = "STAY"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class AFTER_WORK:
|
|
35
|
+
agent: Union[AfterWorkOption, "SwarmAgent", str, Callable]
|
|
36
|
+
|
|
37
|
+
def __post_init__(self):
|
|
38
|
+
if isinstance(self.agent, str):
|
|
39
|
+
self.agent = AfterWorkOption(self.agent.upper())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class ON_CONDITION:
|
|
44
|
+
agent: "SwarmAgent"
|
|
45
|
+
condition: str = ""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def initiate_swarm_chat(
|
|
49
|
+
initial_agent: "SwarmAgent",
|
|
50
|
+
messages: Union[List[Dict[str, Any]], str],
|
|
51
|
+
agents: List["SwarmAgent"],
|
|
52
|
+
user_agent: Optional[UserProxyAgent] = None,
|
|
53
|
+
max_rounds: int = 20,
|
|
54
|
+
context_variables: Optional[Dict[str, Any]] = None,
|
|
55
|
+
after_work: Optional[Union[AFTER_WORK, Callable]] = AFTER_WORK(AfterWorkOption.TERMINATE),
|
|
56
|
+
) -> Tuple[ChatResult, Dict[str, Any], "SwarmAgent"]:
|
|
57
|
+
"""Initialize and run a swarm chat
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
initial_agent: The first receiving agent of the conversation.
|
|
61
|
+
messages: Initial message(s).
|
|
62
|
+
agents: List of swarm agents.
|
|
63
|
+
user_agent: Optional user proxy agent for falling back to.
|
|
64
|
+
max_rounds: Maximum number of conversation rounds.
|
|
65
|
+
context_variables: Starting context variables.
|
|
66
|
+
after_work: Method to handle conversation continuation when an agent doesn't select the next agent. If no agent is selected and no tool calls are output, we will use this method to determine the next agent.
|
|
67
|
+
Must be a AFTER_WORK instance (which is a dataclass accepting a SwarmAgent, AfterWorkOption, A str (of the AfterWorkOption)) or a callable.
|
|
68
|
+
AfterWorkOption:
|
|
69
|
+
- TERMINATE (Default): Terminate the conversation.
|
|
70
|
+
- REVERT_TO_USER : Revert to the user agent if a user agent is provided. If not provided, terminate the conversation.
|
|
71
|
+
- STAY : Stay with the last speaker.
|
|
72
|
+
|
|
73
|
+
Callable: A custom function that takes the current agent, messages, groupchat, and context_variables as arguments and returns the next agent. The function should return None to terminate.
|
|
74
|
+
```python
|
|
75
|
+
def custom_afterwork_func(last_speaker: SwarmAgent, messages: List[Dict[str, Any]], groupchat: GroupChat, context_variables: Optional[Dict[str, Any]]) -> Optional[SwarmAgent]:
|
|
76
|
+
```
|
|
77
|
+
Returns:
|
|
78
|
+
ChatResult: Conversations chat history.
|
|
79
|
+
Dict[str, Any]: Updated Context variables.
|
|
80
|
+
SwarmAgent: Last speaker.
|
|
81
|
+
"""
|
|
82
|
+
assert isinstance(initial_agent, SwarmAgent), "initial_agent must be a SwarmAgent"
|
|
83
|
+
assert all(isinstance(agent, SwarmAgent) for agent in agents), "agents must be a list of SwarmAgents"
|
|
84
|
+
|
|
85
|
+
context_variables = context_variables or {}
|
|
86
|
+
if isinstance(messages, str):
|
|
87
|
+
messages = [{"role": "user", "content": messages}]
|
|
88
|
+
|
|
89
|
+
swarm_agent_names = [agent.name for agent in agents]
|
|
90
|
+
|
|
91
|
+
tool_execution = SwarmAgent(
|
|
92
|
+
name="Tool_Execution",
|
|
93
|
+
system_message="Tool Execution",
|
|
94
|
+
)
|
|
95
|
+
tool_execution._set_to_tool_execution(context_variables=context_variables)
|
|
96
|
+
|
|
97
|
+
# Update tool execution agent with all the functions from all the agents
|
|
98
|
+
for agent in agents:
|
|
99
|
+
tool_execution._function_map.update(agent._function_map)
|
|
100
|
+
|
|
101
|
+
INIT_AGENT_USED = False
|
|
102
|
+
|
|
103
|
+
def swarm_transition(last_speaker: SwarmAgent, groupchat: GroupChat):
|
|
104
|
+
"""Swarm transition function to determine the next agent in the conversation"""
|
|
105
|
+
nonlocal INIT_AGENT_USED
|
|
106
|
+
if not INIT_AGENT_USED:
|
|
107
|
+
INIT_AGENT_USED = True
|
|
108
|
+
return initial_agent
|
|
109
|
+
|
|
110
|
+
if "tool_calls" in groupchat.messages[-1]:
|
|
111
|
+
return tool_execution
|
|
112
|
+
if tool_execution._next_agent is not None:
|
|
113
|
+
next_agent = tool_execution._next_agent
|
|
114
|
+
tool_execution._next_agent = None
|
|
115
|
+
return next_agent
|
|
116
|
+
|
|
117
|
+
# get the last swarm agent
|
|
118
|
+
last_swarm_speaker = None
|
|
119
|
+
for message in reversed(groupchat.messages):
|
|
120
|
+
if "name" in message and message["name"] in swarm_agent_names:
|
|
121
|
+
agent = groupchat.agent_by_name(name=message["name"])
|
|
122
|
+
if isinstance(agent, SwarmAgent):
|
|
123
|
+
last_swarm_speaker = agent
|
|
124
|
+
break
|
|
125
|
+
if last_swarm_speaker is None:
|
|
126
|
+
raise ValueError("No swarm agent found in the message history")
|
|
127
|
+
|
|
128
|
+
# If the user last spoke, return to the agent prior
|
|
129
|
+
if (user_agent and last_speaker == user_agent) or groupchat.messages[-1]["role"] == "tool":
|
|
130
|
+
return last_swarm_speaker
|
|
131
|
+
|
|
132
|
+
# No agent selected via hand-offs (tool calls)
|
|
133
|
+
# Assume the work is Done
|
|
134
|
+
# override if agent-level after_work is defined, else use the global after_work
|
|
135
|
+
tmp_after_work = last_swarm_speaker.after_work if last_swarm_speaker.after_work is not None else after_work
|
|
136
|
+
if isinstance(tmp_after_work, AFTER_WORK):
|
|
137
|
+
tmp_after_work = tmp_after_work.agent
|
|
138
|
+
|
|
139
|
+
if isinstance(tmp_after_work, SwarmAgent):
|
|
140
|
+
return tmp_after_work
|
|
141
|
+
elif isinstance(tmp_after_work, AfterWorkOption):
|
|
142
|
+
if tmp_after_work == AfterWorkOption.TERMINATE or (
|
|
143
|
+
user_agent is None and tmp_after_work == AfterWorkOption.REVERT_TO_USER
|
|
144
|
+
):
|
|
145
|
+
return None
|
|
146
|
+
elif tmp_after_work == AfterWorkOption.REVERT_TO_USER:
|
|
147
|
+
return user_agent
|
|
148
|
+
elif tmp_after_work == AfterWorkOption.STAY:
|
|
149
|
+
return last_speaker
|
|
150
|
+
elif isinstance(tmp_after_work, Callable):
|
|
151
|
+
return tmp_after_work(last_speaker, groupchat.messages, groupchat, context_variables)
|
|
152
|
+
else:
|
|
153
|
+
raise ValueError("Invalid After Work condition")
|
|
154
|
+
|
|
155
|
+
# If there's only one message and there's no identified swarm agent
|
|
156
|
+
# Start with a user proxy agent, creating one if they haven't passed one in
|
|
157
|
+
if len(messages) == 1 and "name" not in messages[0] and not user_agent:
|
|
158
|
+
temp_user_proxy = [UserProxyAgent(name="_User")]
|
|
159
|
+
else:
|
|
160
|
+
temp_user_proxy = []
|
|
161
|
+
|
|
162
|
+
groupchat = GroupChat(
|
|
163
|
+
agents=[tool_execution] + agents + ([user_agent] if user_agent is not None else temp_user_proxy),
|
|
164
|
+
messages=[], # Set to empty. We will resume the conversation with the messages
|
|
165
|
+
max_round=max_rounds,
|
|
166
|
+
speaker_selection_method=swarm_transition,
|
|
167
|
+
)
|
|
168
|
+
manager = GroupChatManager(groupchat)
|
|
169
|
+
clear_history = True
|
|
170
|
+
|
|
171
|
+
if len(messages) > 1:
|
|
172
|
+
last_agent, last_message = manager.resume(messages=messages)
|
|
173
|
+
clear_history = False
|
|
174
|
+
else:
|
|
175
|
+
last_message = messages[0]
|
|
176
|
+
|
|
177
|
+
if "name" in last_message:
|
|
178
|
+
if "name" in swarm_agent_names:
|
|
179
|
+
# If there's a name in the message and it's a swarm agent, use that
|
|
180
|
+
last_agent = groupchat.agent_by_name(name=last_message["name"])
|
|
181
|
+
else:
|
|
182
|
+
raise ValueError(f"Invalid swarm agent name in last message: {last_message['name']}")
|
|
183
|
+
else:
|
|
184
|
+
# No name, so we're using the user proxy to start the conversation
|
|
185
|
+
if user_agent:
|
|
186
|
+
last_agent = user_agent
|
|
187
|
+
else:
|
|
188
|
+
# If no user agent passed in, use our temporary user proxy
|
|
189
|
+
last_agent = temp_user_proxy[0]
|
|
190
|
+
|
|
191
|
+
chat_result = last_agent.initiate_chat(
|
|
192
|
+
manager,
|
|
193
|
+
message=last_message,
|
|
194
|
+
clear_history=clear_history,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Clear the temporary user proxy's name from messages
|
|
198
|
+
if len(temp_user_proxy) == 1:
|
|
199
|
+
for message in chat_result.chat_history:
|
|
200
|
+
if "name" in message and message["name"] == "_User":
|
|
201
|
+
# delete the name key from the message
|
|
202
|
+
del message["name"]
|
|
203
|
+
|
|
204
|
+
return chat_result, context_variables, manager.last_speaker
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class SwarmResult(BaseModel):
|
|
208
|
+
"""
|
|
209
|
+
Encapsulates the possible return values for a swarm agent function.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
values (str): The result values as a string.
|
|
213
|
+
agent (SwarmAgent): The swarm agent instance, if applicable.
|
|
214
|
+
context_variables (dict): A dictionary of context variables.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
values: str = ""
|
|
218
|
+
agent: Optional["SwarmAgent"] = None
|
|
219
|
+
context_variables: Dict[str, Any] = {}
|
|
220
|
+
|
|
221
|
+
class Config: # Add this inner class
|
|
222
|
+
arbitrary_types_allowed = True
|
|
223
|
+
|
|
224
|
+
def __str__(self):
|
|
225
|
+
return self.values
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class SwarmAgent(ConversableAgent):
|
|
229
|
+
"""Swarm agent for participating in a swarm.
|
|
230
|
+
|
|
231
|
+
SwarmAgent is a subclass of ConversableAgent.
|
|
232
|
+
|
|
233
|
+
Additional args:
|
|
234
|
+
functions (List[Callable]): A list of functions to register with the agent.
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
def __init__(
|
|
238
|
+
self,
|
|
239
|
+
name: str,
|
|
240
|
+
system_message: Optional[str] = "You are a helpful AI Assistant.",
|
|
241
|
+
llm_config: Optional[Union[Dict, Literal[False]]] = None,
|
|
242
|
+
functions: Union[List[Callable], Callable] = None,
|
|
243
|
+
is_termination_msg: Optional[Callable[[Dict], bool]] = None,
|
|
244
|
+
max_consecutive_auto_reply: Optional[int] = None,
|
|
245
|
+
human_input_mode: Literal["ALWAYS", "NEVER", "TERMINATE"] = "NEVER",
|
|
246
|
+
description: Optional[str] = None,
|
|
247
|
+
code_execution_config=False,
|
|
248
|
+
**kwargs,
|
|
249
|
+
) -> None:
|
|
250
|
+
super().__init__(
|
|
251
|
+
name,
|
|
252
|
+
system_message,
|
|
253
|
+
is_termination_msg,
|
|
254
|
+
max_consecutive_auto_reply,
|
|
255
|
+
human_input_mode,
|
|
256
|
+
llm_config=llm_config,
|
|
257
|
+
description=description,
|
|
258
|
+
code_execution_config=code_execution_config,
|
|
259
|
+
**kwargs,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if isinstance(functions, list):
|
|
263
|
+
self.add_functions(functions)
|
|
264
|
+
elif isinstance(functions, Callable):
|
|
265
|
+
self.add_single_function(functions)
|
|
266
|
+
|
|
267
|
+
self.after_work = None
|
|
268
|
+
|
|
269
|
+
# use in the tool execution agent to transfer to the next agent
|
|
270
|
+
self._context_variables = {}
|
|
271
|
+
self._next_agent = None
|
|
272
|
+
|
|
273
|
+
def _set_to_tool_execution(self, context_variables: Optional[Dict[str, Any]] = None):
|
|
274
|
+
"""Set to a special instance of SwarmAgent that is responsible for executing tool calls from other swarm agents.
|
|
275
|
+
This agent will be used internally and should not be visible to the user.
|
|
276
|
+
|
|
277
|
+
It will execute the tool calls and update the context_variables and next_agent accordingly.
|
|
278
|
+
"""
|
|
279
|
+
self._next_agent = None
|
|
280
|
+
self._context_variables = context_variables or {}
|
|
281
|
+
self._reply_func_list.clear()
|
|
282
|
+
self.register_reply([Agent, None], SwarmAgent.generate_swarm_tool_reply)
|
|
283
|
+
|
|
284
|
+
def __str__(self):
|
|
285
|
+
return f"SwarmAgent --> {self.name}"
|
|
286
|
+
|
|
287
|
+
def register_hand_off(
|
|
288
|
+
self,
|
|
289
|
+
hand_to: Union[List[Union[ON_CONDITION, AFTER_WORK]], ON_CONDITION, AFTER_WORK],
|
|
290
|
+
):
|
|
291
|
+
"""Register a function to hand off to another agent.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
hand_to: A list of ON_CONDITIONs and an, optional, AFTER_WORK condition
|
|
295
|
+
|
|
296
|
+
Hand off template:
|
|
297
|
+
def transfer_to_agent_name() -> SwarmAgent:
|
|
298
|
+
return agent_name
|
|
299
|
+
1. register the function with the agent
|
|
300
|
+
2. register the schema with the agent, description set to the condition
|
|
301
|
+
"""
|
|
302
|
+
if isinstance(hand_to, (ON_CONDITION, AFTER_WORK)):
|
|
303
|
+
hand_to = [hand_to]
|
|
304
|
+
|
|
305
|
+
for transit in hand_to:
|
|
306
|
+
if isinstance(transit, AFTER_WORK):
|
|
307
|
+
self.after_work = transit
|
|
308
|
+
elif isinstance(transit, ON_CONDITION):
|
|
309
|
+
|
|
310
|
+
# Create closure with current loop transit value
|
|
311
|
+
# to ensure the condition matches the one in the loop
|
|
312
|
+
def make_transfer_function(current_transit):
|
|
313
|
+
def transfer_to_agent() -> "SwarmAgent":
|
|
314
|
+
return current_transit.agent
|
|
315
|
+
|
|
316
|
+
return transfer_to_agent
|
|
317
|
+
|
|
318
|
+
transfer_func = make_transfer_function(transit)
|
|
319
|
+
self.add_single_function(transfer_func, f"transfer_to_{transit.agent.name}", transit.condition)
|
|
320
|
+
else:
|
|
321
|
+
raise ValueError("Invalid hand off condition, must be either ON_CONDITION or AFTER_WORK")
|
|
322
|
+
|
|
323
|
+
def generate_swarm_tool_reply(
|
|
324
|
+
self,
|
|
325
|
+
messages: Optional[List[Dict]] = None,
|
|
326
|
+
sender: Optional[Agent] = None,
|
|
327
|
+
config: Optional[OpenAIWrapper] = None,
|
|
328
|
+
) -> Tuple[bool, dict]:
|
|
329
|
+
"""Pre-processes and generates tool call replies.
|
|
330
|
+
|
|
331
|
+
This function:
|
|
332
|
+
1. Adds context_variables back to the tool call for the function, if necessary.
|
|
333
|
+
2. Generates the tool calls reply.
|
|
334
|
+
3. Updates context_variables and next_agent based on the tool call response."""
|
|
335
|
+
|
|
336
|
+
if config is None:
|
|
337
|
+
config = self
|
|
338
|
+
if messages is None:
|
|
339
|
+
messages = self._oai_messages[sender]
|
|
340
|
+
|
|
341
|
+
message = messages[-1]
|
|
342
|
+
if "tool_calls" in message:
|
|
343
|
+
# 1. add context_variables to the tool call arguments
|
|
344
|
+
for tool_call in message["tool_calls"]:
|
|
345
|
+
if tool_call["type"] == "function":
|
|
346
|
+
function_name = tool_call["function"]["name"]
|
|
347
|
+
|
|
348
|
+
# Check if this function exists in our function map
|
|
349
|
+
if function_name in self._function_map:
|
|
350
|
+
func = self._function_map[function_name] # Get the original function
|
|
351
|
+
|
|
352
|
+
# Check if function has context_variables parameter
|
|
353
|
+
sig = signature(func)
|
|
354
|
+
if __CONTEXT_VARIABLES_PARAM_NAME__ in sig.parameters:
|
|
355
|
+
current_args = json.loads(tool_call["function"]["arguments"])
|
|
356
|
+
current_args[__CONTEXT_VARIABLES_PARAM_NAME__] = self._context_variables
|
|
357
|
+
# Update the tool call with new arguments
|
|
358
|
+
tool_call["function"]["arguments"] = json.dumps(current_args)
|
|
359
|
+
|
|
360
|
+
# 2. generate tool calls reply
|
|
361
|
+
_, tool_message = self.generate_tool_calls_reply([message])
|
|
362
|
+
|
|
363
|
+
# 3. update context_variables and next_agent, convert content to string
|
|
364
|
+
for tool_response in tool_message["tool_responses"]:
|
|
365
|
+
content = tool_response.get("content")
|
|
366
|
+
if isinstance(content, SwarmResult):
|
|
367
|
+
if content.context_variables != {}:
|
|
368
|
+
self._context_variables.update(content.context_variables)
|
|
369
|
+
if content.agent is not None:
|
|
370
|
+
self._next_agent = content.agent
|
|
371
|
+
elif isinstance(content, Agent):
|
|
372
|
+
self._next_agent = content
|
|
373
|
+
tool_response["content"] = str(tool_response["content"])
|
|
374
|
+
|
|
375
|
+
return True, tool_message
|
|
376
|
+
return False, None
|
|
377
|
+
|
|
378
|
+
def add_single_function(self, func: Callable, name=None, description=""):
|
|
379
|
+
if name:
|
|
380
|
+
func._name = name
|
|
381
|
+
else:
|
|
382
|
+
func._name = func.__name__
|
|
383
|
+
|
|
384
|
+
if description:
|
|
385
|
+
func._description = description
|
|
386
|
+
else:
|
|
387
|
+
# Use function's docstring, strip whitespace, fall back to empty string
|
|
388
|
+
func._description = (func.__doc__ or "").strip()
|
|
389
|
+
|
|
390
|
+
f = get_function_schema(func, name=func._name, description=func._description)
|
|
391
|
+
|
|
392
|
+
# Remove context_variables parameter from function schema
|
|
393
|
+
f_no_context = f.copy()
|
|
394
|
+
if __CONTEXT_VARIABLES_PARAM_NAME__ in f_no_context["function"]["parameters"]["properties"]:
|
|
395
|
+
del f_no_context["function"]["parameters"]["properties"][__CONTEXT_VARIABLES_PARAM_NAME__]
|
|
396
|
+
if "required" in f_no_context["function"]["parameters"]:
|
|
397
|
+
required = f_no_context["function"]["parameters"]["required"]
|
|
398
|
+
f_no_context["function"]["parameters"]["required"] = [
|
|
399
|
+
param for param in required if param != __CONTEXT_VARIABLES_PARAM_NAME__
|
|
400
|
+
]
|
|
401
|
+
# If required list is empty, remove it
|
|
402
|
+
if not f_no_context["function"]["parameters"]["required"]:
|
|
403
|
+
del f_no_context["function"]["parameters"]["required"]
|
|
404
|
+
|
|
405
|
+
self.update_tool_signature(f_no_context, is_remove=False)
|
|
406
|
+
self.register_function({func._name: func})
|
|
407
|
+
|
|
408
|
+
def add_functions(self, func_list: List[Callable]):
|
|
409
|
+
for func in func_list:
|
|
410
|
+
self.add_single_function(func)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
# Forward references for SwarmAgent in SwarmResult
|
|
414
|
+
SwarmResult.update_forward_refs()
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import inspect
|
|
3
|
+
import os
|
|
4
|
+
from textwrap import dedent, indent
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from sentence_transformers import SentenceTransformer, util
|
|
8
|
+
|
|
9
|
+
from autogen import AssistantAgent, UserProxyAgent
|
|
10
|
+
from autogen.coding import LocalCommandLineCodeExecutor
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ToolBuilder:
|
|
14
|
+
TOOL_USING_PROMPT = """# Functions
|
|
15
|
+
You have access to the following functions. They can be accessed from the module called 'functions' by their function names.
|
|
16
|
+
For example, if there is a function called `foo` you could import it by writing `from functions import foo`
|
|
17
|
+
{functions}
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, corpus_path, retriever="all-mpnet-base-v2"):
|
|
21
|
+
|
|
22
|
+
self.df = pd.read_csv(corpus_path, sep="\t")
|
|
23
|
+
document_list = self.df["document_content"].tolist()
|
|
24
|
+
|
|
25
|
+
self.model = SentenceTransformer(retriever)
|
|
26
|
+
self.embeddings = self.model.encode(document_list)
|
|
27
|
+
|
|
28
|
+
def retrieve(self, query, top_k=3):
|
|
29
|
+
# Encode the query using the Sentence Transformer model
|
|
30
|
+
query_embedding = self.model.encode([query])
|
|
31
|
+
|
|
32
|
+
hits = util.semantic_search(query_embedding, self.embeddings, top_k=top_k)
|
|
33
|
+
|
|
34
|
+
results = []
|
|
35
|
+
for hit in hits[0]:
|
|
36
|
+
results.append(self.df.iloc[hit["corpus_id"], 1])
|
|
37
|
+
return results
|
|
38
|
+
|
|
39
|
+
def bind(self, agent: AssistantAgent, functions: str):
|
|
40
|
+
"""Binds the function to the agent so that agent is aware of it."""
|
|
41
|
+
sys_message = agent.system_message
|
|
42
|
+
sys_message += self.TOOL_USING_PROMPT.format(functions=functions)
|
|
43
|
+
agent.update_system_message(sys_message)
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
def bind_user_proxy(self, agent: UserProxyAgent, tool_root: str):
|
|
47
|
+
"""
|
|
48
|
+
Updates user proxy agent with a executor so that code executor can successfully execute function-related code.
|
|
49
|
+
Returns an updated user proxy.
|
|
50
|
+
"""
|
|
51
|
+
# Find all the functions in the tool root
|
|
52
|
+
functions = find_callables(tool_root)
|
|
53
|
+
|
|
54
|
+
code_execution_config = agent._code_execution_config
|
|
55
|
+
executor = LocalCommandLineCodeExecutor(
|
|
56
|
+
timeout=code_execution_config.get("timeout", 180),
|
|
57
|
+
work_dir=code_execution_config.get("work_dir", "coding"),
|
|
58
|
+
functions=functions,
|
|
59
|
+
)
|
|
60
|
+
code_execution_config = {
|
|
61
|
+
"executor": executor,
|
|
62
|
+
"last_n_messages": code_execution_config.get("last_n_messages", 1),
|
|
63
|
+
}
|
|
64
|
+
updated_user_proxy = UserProxyAgent(
|
|
65
|
+
name=agent.name,
|
|
66
|
+
is_termination_msg=agent._is_termination_msg,
|
|
67
|
+
code_execution_config=code_execution_config,
|
|
68
|
+
human_input_mode="NEVER",
|
|
69
|
+
default_auto_reply=agent._default_auto_reply,
|
|
70
|
+
)
|
|
71
|
+
return updated_user_proxy
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_full_tool_description(py_file):
|
|
75
|
+
"""
|
|
76
|
+
Retrieves the function signature for a given Python file.
|
|
77
|
+
"""
|
|
78
|
+
with open(py_file, "r") as f:
|
|
79
|
+
code = f.read()
|
|
80
|
+
exec(code)
|
|
81
|
+
function_name = os.path.splitext(os.path.basename(py_file))[0]
|
|
82
|
+
if function_name in locals():
|
|
83
|
+
func = locals()[function_name]
|
|
84
|
+
content = f"def {func.__name__}{inspect.signature(func)}:\n"
|
|
85
|
+
docstring = func.__doc__
|
|
86
|
+
|
|
87
|
+
if docstring:
|
|
88
|
+
docstring = dedent(docstring)
|
|
89
|
+
docstring = '"""' + docstring + '"""'
|
|
90
|
+
docstring = indent(docstring, " ")
|
|
91
|
+
content += docstring + "\n"
|
|
92
|
+
return content
|
|
93
|
+
else:
|
|
94
|
+
raise ValueError(f"Function {function_name} not found in {py_file}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def find_callables(directory):
|
|
98
|
+
"""
|
|
99
|
+
Find all callable objects defined in Python files within the specified directory.
|
|
100
|
+
"""
|
|
101
|
+
callables = []
|
|
102
|
+
for root, dirs, files in os.walk(directory):
|
|
103
|
+
for file in files:
|
|
104
|
+
if file.endswith(".py"):
|
|
105
|
+
module_name = os.path.splitext(file)[0]
|
|
106
|
+
module_path = os.path.join(root, file)
|
|
107
|
+
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
|
108
|
+
module = importlib.util.module_from_spec(spec)
|
|
109
|
+
spec.loader.exec_module(module)
|
|
110
|
+
for name, value in module.__dict__.items():
|
|
111
|
+
if callable(value) and name == module_name:
|
|
112
|
+
callables.append(value)
|
|
113
|
+
break
|
|
114
|
+
return callables
|
|
@@ -164,7 +164,7 @@ class ConversableAgent(LLMAgent):
|
|
|
164
164
|
except TypeError as e:
|
|
165
165
|
raise TypeError(
|
|
166
166
|
"Please implement __deepcopy__ method for each value class in llm_config to support deepcopy."
|
|
167
|
-
" Refer to the docs for more details: https://ag2ai.github.io/
|
|
167
|
+
" Refer to the docs for more details: https://ag2ai.github.io/ag2/docs/topics/llm_configuration#adding-http-client-in-llm_config-for-proxy"
|
|
168
168
|
) from e
|
|
169
169
|
|
|
170
170
|
self._validate_llm_config(llm_config)
|
|
@@ -659,6 +659,9 @@ class ConversableAgent(LLMAgent):
|
|
|
659
659
|
|
|
660
660
|
if message.get("role") in ["function", "tool"]:
|
|
661
661
|
oai_message["role"] = message.get("role")
|
|
662
|
+
if "tool_responses" in oai_message:
|
|
663
|
+
for tool_response in oai_message["tool_responses"]:
|
|
664
|
+
tool_response["content"] = str(tool_response["content"])
|
|
662
665
|
elif "override_role" in message:
|
|
663
666
|
# If we have a direction to override the role then set the
|
|
664
667
|
# role accordingly. Used to customise the role for the
|
|
@@ -791,15 +794,16 @@ class ConversableAgent(LLMAgent):
|
|
|
791
794
|
"Message can't be converted into a valid ChatCompletion message. Either content or function_call must be provided."
|
|
792
795
|
)
|
|
793
796
|
|
|
794
|
-
def _print_received_message(self, message: Union[Dict, str], sender: Agent):
|
|
797
|
+
def _print_received_message(self, message: Union[Dict, str], sender: Agent, skip_head: bool = False):
|
|
795
798
|
iostream = IOStream.get_default()
|
|
796
799
|
# print the message received
|
|
797
|
-
|
|
800
|
+
if not skip_head:
|
|
801
|
+
iostream.print(colored(sender.name, "yellow"), "(to", f"{self.name}):\n", flush=True)
|
|
798
802
|
message = self._message_to_dict(message)
|
|
799
803
|
|
|
800
804
|
if message.get("tool_responses"): # Handle tool multi-call responses
|
|
801
805
|
for tool_response in message["tool_responses"]:
|
|
802
|
-
self._print_received_message(tool_response, sender)
|
|
806
|
+
self._print_received_message(tool_response, sender, skip_head=True)
|
|
803
807
|
if message.get("role") == "tool":
|
|
804
808
|
return # If role is tool, then content is just a concatenation of all tool_responses
|
|
805
809
|
|
|
@@ -2288,7 +2292,7 @@ class ConversableAgent(LLMAgent):
|
|
|
2288
2292
|
result.append(char)
|
|
2289
2293
|
return "".join(result)
|
|
2290
2294
|
|
|
2291
|
-
def execute_function(self, func_call, verbose: bool = False) -> Tuple[bool, Dict[str,
|
|
2295
|
+
def execute_function(self, func_call, verbose: bool = False) -> Tuple[bool, Dict[str, Any]]:
|
|
2292
2296
|
"""Execute a function call and return the result.
|
|
2293
2297
|
|
|
2294
2298
|
Override this function to modify the way to execute function and tool calls.
|
|
@@ -2342,7 +2346,7 @@ class ConversableAgent(LLMAgent):
|
|
|
2342
2346
|
return is_exec_success, {
|
|
2343
2347
|
"name": func_name,
|
|
2344
2348
|
"role": "function",
|
|
2345
|
-
"content":
|
|
2349
|
+
"content": content,
|
|
2346
2350
|
}
|
|
2347
2351
|
|
|
2348
2352
|
async def a_execute_function(self, func_call):
|
|
@@ -2397,7 +2401,7 @@ class ConversableAgent(LLMAgent):
|
|
|
2397
2401
|
return is_exec_success, {
|
|
2398
2402
|
"name": func_name,
|
|
2399
2403
|
"role": "function",
|
|
2400
|
-
"content":
|
|
2404
|
+
"content": content,
|
|
2401
2405
|
}
|
|
2402
2406
|
|
|
2403
2407
|
def generate_init_message(self, message: Union[Dict, str, None], **kwargs) -> Union[str, Dict]:
|
autogen/agentchat/groupchat.py
CHANGED
|
@@ -294,6 +294,8 @@ class GroupChat:
|
|
|
294
294
|
# if the role is tool, it is OK to modify the name
|
|
295
295
|
if message["role"] != "function":
|
|
296
296
|
message["name"] = speaker.name
|
|
297
|
+
if not isinstance(message["content"], str) and not isinstance(message["content"], list):
|
|
298
|
+
message["content"] = str(message["content"])
|
|
297
299
|
message["content"] = content_str(message["content"])
|
|
298
300
|
self.messages.append(message)
|
|
299
301
|
|
autogen/oai/completion.py
CHANGED
|
@@ -742,7 +742,7 @@ class Completion(openai_Completion):
|
|
|
742
742
|
E.g., `prompt="Complete the following sentence: {prefix}, context={"prefix": "Today I feel"}`.
|
|
743
743
|
The actual prompt will be:
|
|
744
744
|
"Complete the following sentence: Today I feel".
|
|
745
|
-
More examples can be found at [templating](https://ag2ai.github.io/
|
|
745
|
+
More examples can be found at [templating](https://ag2ai.github.io/ag2/docs/Use-Cases/enhanced_inference#templating).
|
|
746
746
|
use_cache (bool, Optional): Whether to use cached responses.
|
|
747
747
|
config_list (List, Optional): List of configurations for the completion to try.
|
|
748
748
|
The first one that does not raise an error will be used.
|
|
@@ -804,7 +804,7 @@ class Completion(openai_Completion):
|
|
|
804
804
|
logger.warning(
|
|
805
805
|
"Completion.create is deprecated in autogen, pyautogen v0.2 and openai>=1. "
|
|
806
806
|
"The new openai requires initiating a client for inference. "
|
|
807
|
-
"Please refer to https://ag2ai.github.io/
|
|
807
|
+
"Please refer to https://ag2ai.github.io/ag2/docs/Use-Cases/enhanced_inference#api-unification"
|
|
808
808
|
)
|
|
809
809
|
if ERROR:
|
|
810
810
|
raise ERROR
|
autogen/oai/gemini.py
CHANGED
|
@@ -171,7 +171,7 @@ class GeminiClient:
|
|
|
171
171
|
raise ValueError(
|
|
172
172
|
"Please provide a model name for the Gemini Client. "
|
|
173
173
|
"You can configure it in the OAI Config List file. "
|
|
174
|
-
"See this [LLM configuration tutorial](https://ag2ai.github.io/
|
|
174
|
+
"See this [LLM configuration tutorial](https://ag2ai.github.io/ag2/docs/topics/llm_configuration/) for more details."
|
|
175
175
|
)
|
|
176
176
|
|
|
177
177
|
params.get("api_type", "google") # not used
|
autogen/version.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|