Skip to content

Commit 4222096

Browse files
yenifyiranwu0sonichiekzhu
authored
[Core] [Tool Call] adjust conversable agent to support tool_calls (microsoft#974)
* adjust conversable and compressible agents to support tool_calls * split out tools into their own reply def * copilot typo * address review comments * revert compressible_agent and token_count_utils calls * cleanup terminate check and remove unnecessary code * doc search and update * return function/tool calls as interrupted when user provides a reply to a tool call request * fix tool name reference * fix formatting * fix initiate receiving a dict * missed changed roled * ignore incoming role, more similiar to existing code * consistency * redundant to_dict * fix todo comment * uneeded change * handle dict reply in groupchat * Fix generate_tool_call_calls_reply_comment * change method annotation for register_for_llm from functions to tools * typo autogen/agentchat/conversable_agent.py Co-authored-by: Chi Wang <[email protected]> * add deprecation comments for function_call * tweak doc strings * switch to ToolFunction type * update the return to * fix generate_init_message return type * Revert "fix generate_init_message return type" This reverts commit 645ba8b. * undo force init to dict * fix notebooks and groupchat tool handling * fix type * use get for key error * fix teachable to pull content from dict * change single message tool response * cleanup unnessary changes * little better tool response concatenation * update tools tests * add skip openai check to tools tests * fix nits * move func name normalization to oai_reply and assert configured names * fix whitespace * remove extra normalize * tool name is now normalized in the generate_reply function, so will not be incorrect when sent to receive * validate function names in init and expand comments for validation methods * fix dict comprehension * Dummy llm config for unit tests * handle tool_calls set to None * fix tool name reference * method operates on responses not calls --------- Co-authored-by: Yiran Wu <[email protected]> Co-authored-by: Chi Wang <[email protected]> Co-authored-by: Eric Zhu <[email protected]>
1 parent 9b15939 commit 4222096

9 files changed

+778
-211
lines changed

autogen/agentchat/conversable_agent.py

+309-40
Large diffs are not rendered by default.

autogen/agentchat/groupchat.py

+25-6
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,21 @@ def _prepare_and_select_agents(self, last_speaker: Agent) -> Tuple[Optional[Agen
155155
"Or, use direct communication instead."
156156
)
157157

158-
if self.func_call_filter and self.messages and "function_call" in self.messages[-1]:
158+
if (
159+
self.func_call_filter
160+
and self.messages
161+
and ("function_call" in self.messages[-1] or "tool_calls" in self.messages[-1])
162+
):
163+
funcs = []
164+
if "function_call" in self.messages[-1]:
165+
funcs += [self.messages[-1]["function_call"]["name"]]
166+
if "tool_calls" in self.messages[-1]:
167+
funcs += [
168+
tool["function"]["name"] for tool in self.messages[-1]["tool_calls"] if tool["type"] == "function"
169+
]
170+
159171
# find agents with the right function_map which contains the function name
160-
agents = [
161-
agent for agent in self.agents if agent.can_execute_function(self.messages[-1]["function_call"]["name"])
162-
]
172+
agents = [agent for agent in self.agents if agent.can_execute_function(funcs)]
163173
if len(agents) == 1:
164174
# only one agent can execute the function
165175
return agents[0], agents
@@ -170,7 +180,7 @@ def _prepare_and_select_agents(self, last_speaker: Agent) -> Tuple[Optional[Agen
170180
return agents[0], agents
171181
elif not agents:
172182
raise ValueError(
173-
f"No agent can execute the function {self.messages[-1]['function_call']['name']}. "
183+
f"No agent can execute the function {', '.join(funcs)}. "
174184
"Please check the function_map of the agents."
175185
)
176186
# remove the last speaker from the list to avoid selecting the same speaker if allow_repeat_speaker is False
@@ -193,7 +203,14 @@ def select_speaker(self, last_speaker: Agent, selector: ConversableAgent):
193203
return selected_agent
194204
# auto speaker selection
195205
selector.update_system_message(self.select_speaker_msg(agents))
196-
context = self.messages + [{"role": "system", "content": self.select_speaker_prompt(agents)}]
206+
207+
# If last message is a tool call or function call, blank the call so the api doesn't throw
208+
messages = self.messages.copy()
209+
if messages[-1].get("function_call", False):
210+
messages[-1] = dict(messages[-1], function_call=None)
211+
if messages[-1].get("tool_calls", False):
212+
messages[-1] = dict(messages[-1], tool_calls=None)
213+
context = messages + [{"role": "system", "content": self.select_speaker_prompt(agents)}]
197214
final, name = selector.generate_oai_reply(context)
198215

199216
if not final:
@@ -275,6 +292,8 @@ def _mentioned_agents(self, message_content: Union[str, List], agents: List[Agen
275292
Dict: a counter for mentioned agents.
276293
"""
277294
# Cast message content to str
295+
if isinstance(message_content, dict):
296+
message_content = message_content["content"]
278297
message_content = content_str(message_content)
279298

280299
mentions = dict()

autogen/function_utils.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ class Function(BaseModel):
103103
parameters: Annotated[Parameters, Field(description="Parameters of the function")]
104104

105105

106+
class ToolFunction(BaseModel):
107+
"""A function under tool as defined by the OpenAI API."""
108+
109+
type: Literal["function"] = "function"
110+
function: Annotated[Function, Field(description="Function under tool")]
111+
112+
106113
def get_parameter_json_schema(
107114
k: str, v: Union[Annotated[Type, str], Type], default_values: Dict[str, Any]
108115
) -> JsonSchemaValue:
@@ -260,10 +267,12 @@ def f(a: Annotated[str, "Parameter a"], b: int = 2, c: Annotated[float, "Paramet
260267

261268
parameters = get_parameters(required, param_annotations, default_values=default_values)
262269

263-
function = Function(
264-
description=description,
265-
name=fname,
266-
parameters=parameters,
270+
function = ToolFunction(
271+
function=Function(
272+
description=description,
273+
name=fname,
274+
parameters=parameters,
275+
)
267276
)
268277

269278
return model_dump(function)

autogen/oai/client.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,9 @@ def yes_or_no_filter(context, response):
287287

288288
def _completions_create(self, client, params):
289289
completions = client.chat.completions if "messages" in params else client.completions
290-
# If streaming is enabled, has messages, and does not have functions, then
290+
# If streaming is enabled, has messages, and does not have functions or tools, then
291291
# iterate over the chunks of the response
292-
if params.get("stream", False) and "messages" in params and "functions" not in params:
292+
if params.get("stream", False) and "messages" in params and "functions" not in params and "tools" not in params:
293293
response_contents = [""] * params.get("n", 1)
294294
finish_reasons = [""] * params.get("n", 1)
295295
completion_tokens = 0
@@ -352,8 +352,8 @@ def _completions_create(self, client, params):
352352

353353
response.choices.append(choice)
354354
else:
355-
# If streaming is not enabled or using functions, send a regular chat completion request
356-
# Functions are not supported, so ensure streaming is disabled
355+
# If streaming is not enabled, using functions, or tools, send a regular chat completion request
356+
# Functions and Tools are not supported, so ensure streaming is disabled
357357
params = params.copy()
358358
params["stream"] = False
359359
response = completions.create(**params)

notebook/agentchat_function_call_currency_calculator.ipynb

+67-49
Original file line numberDiff line numberDiff line change
@@ -185,20 +185,21 @@
185185
{
186186
"data": {
187187
"text/plain": [
188-
"[{'description': 'Currency exchange calculator.',\n",
189-
" 'name': 'currency_calculator',\n",
190-
" 'parameters': {'type': 'object',\n",
191-
" 'properties': {'base_amount': {'type': 'number',\n",
192-
" 'description': 'Amount of currency in base_currency'},\n",
193-
" 'base_currency': {'enum': ['USD', 'EUR'],\n",
194-
" 'type': 'string',\n",
195-
" 'default': 'USD',\n",
196-
" 'description': 'Base currency'},\n",
197-
" 'quote_currency': {'enum': ['USD', 'EUR'],\n",
198-
" 'type': 'string',\n",
199-
" 'default': 'EUR',\n",
200-
" 'description': 'Quote currency'}},\n",
201-
" 'required': ['base_amount']}}]"
188+
"[{'type': 'function',\n",
189+
" 'function': {'description': 'Currency exchange calculator.',\n",
190+
" 'name': 'currency_calculator',\n",
191+
" 'parameters': {'type': 'object',\n",
192+
" 'properties': {'base_amount': {'type': 'number',\n",
193+
" 'description': 'Amount of currency in base_currency'},\n",
194+
" 'base_currency': {'enum': ['USD', 'EUR'],\n",
195+
" 'type': 'string',\n",
196+
" 'default': 'USD',\n",
197+
" 'description': 'Base currency'},\n",
198+
" 'quote_currency': {'enum': ['USD', 'EUR'],\n",
199+
" 'type': 'string',\n",
200+
" 'default': 'EUR',\n",
201+
" 'description': 'Quote currency'}},\n",
202+
" 'required': ['base_amount']}}}]"
202203
]
203204
},
204205
"execution_count": 4,
@@ -207,7 +208,7 @@
207208
}
208209
],
209210
"source": [
210-
"chatbot.llm_config[\"functions\"]"
211+
"chatbot.llm_config[\"tools\"]"
211212
]
212213
},
213214
{
@@ -259,10 +260,14 @@
259260
"--------------------------------------------------------------------------------\n",
260261
"\u001b[33mchatbot\u001b[0m (to user_proxy):\n",
261262
"\n",
262-
"\u001b[32m***** Suggested function Call: currency_calculator *****\u001b[0m\n",
263+
"\u001b[32m***** Suggested tool Call (call_2mZCDF9fe8WJh6SveIwdGGEy): currency_calculator *****\u001b[0m\n",
263264
"Arguments: \n",
264-
"{\"base_amount\":123.45,\"base_currency\":\"USD\",\"quote_currency\":\"EUR\"}\n",
265-
"\u001b[32m********************************************************\u001b[0m\n",
265+
"{\n",
266+
" \"base_amount\": 123.45,\n",
267+
" \"base_currency\": \"USD\",\n",
268+
" \"quote_currency\": \"EUR\"\n",
269+
"}\n",
270+
"\u001b[32m************************************************************************************\u001b[0m\n",
266271
"\n",
267272
"--------------------------------------------------------------------------------\n",
268273
"\u001b[35m\n",
@@ -276,7 +281,7 @@
276281
"--------------------------------------------------------------------------------\n",
277282
"\u001b[33mchatbot\u001b[0m (to user_proxy):\n",
278283
"\n",
279-
"123.45 USD is equivalent to approximately 112.23 EUR.\n",
284+
"123.45 USD is approximately 112.23 EUR.\n",
280285
"\n",
281286
"--------------------------------------------------------------------------------\n",
282287
"\u001b[33muser_proxy\u001b[0m (to chatbot):\n",
@@ -370,27 +375,28 @@
370375
{
371376
"data": {
372377
"text/plain": [
373-
"[{'description': 'Currency exchange calculator.',\n",
374-
" 'name': 'currency_calculator',\n",
375-
" 'parameters': {'type': 'object',\n",
376-
" 'properties': {'base': {'properties': {'currency': {'description': 'Currency symbol',\n",
377-
" 'enum': ['USD', 'EUR'],\n",
378-
" 'title': 'Currency',\n",
379-
" 'type': 'string'},\n",
380-
" 'amount': {'default': 0,\n",
381-
" 'description': 'Amount of currency',\n",
382-
" 'minimum': 0.0,\n",
383-
" 'title': 'Amount',\n",
384-
" 'type': 'number'}},\n",
385-
" 'required': ['currency'],\n",
386-
" 'title': 'Currency',\n",
387-
" 'type': 'object',\n",
388-
" 'description': 'Base currency: amount and currency symbol'},\n",
389-
" 'quote_currency': {'enum': ['USD', 'EUR'],\n",
390-
" 'type': 'string',\n",
391-
" 'default': 'USD',\n",
392-
" 'description': 'Quote currency symbol'}},\n",
393-
" 'required': ['base']}}]"
378+
"[{'type': 'function',\n",
379+
" 'function': {'description': 'Currency exchange calculator.',\n",
380+
" 'name': 'currency_calculator',\n",
381+
" 'parameters': {'type': 'object',\n",
382+
" 'properties': {'base': {'properties': {'currency': {'description': 'Currency symbol',\n",
383+
" 'enum': ['USD', 'EUR'],\n",
384+
" 'title': 'Currency',\n",
385+
" 'type': 'string'},\n",
386+
" 'amount': {'default': 0,\n",
387+
" 'description': 'Amount of currency',\n",
388+
" 'minimum': 0.0,\n",
389+
" 'title': 'Amount',\n",
390+
" 'type': 'number'}},\n",
391+
" 'required': ['currency'],\n",
392+
" 'title': 'Currency',\n",
393+
" 'type': 'object',\n",
394+
" 'description': 'Base currency: amount and currency symbol'},\n",
395+
" 'quote_currency': {'enum': ['USD', 'EUR'],\n",
396+
" 'type': 'string',\n",
397+
" 'default': 'USD',\n",
398+
" 'description': 'Quote currency symbol'}},\n",
399+
" 'required': ['base']}}}]"
394400
]
395401
},
396402
"execution_count": 8,
@@ -399,7 +405,7 @@
399405
}
400406
],
401407
"source": [
402-
"chatbot.llm_config[\"functions\"]"
408+
"chatbot.llm_config[\"tools\"]"
403409
]
404410
},
405411
{
@@ -419,10 +425,16 @@
419425
"--------------------------------------------------------------------------------\n",
420426
"\u001b[33mchatbot\u001b[0m (to user_proxy):\n",
421427
"\n",
422-
"\u001b[32m***** Suggested function Call: currency_calculator *****\u001b[0m\n",
428+
"\u001b[32m***** Suggested tool Call (call_MLtsPcVJXhdpvDPNNxfTB3OB): currency_calculator *****\u001b[0m\n",
423429
"Arguments: \n",
424-
"{\"base\":{\"currency\":\"EUR\",\"amount\":112.23},\"quote_currency\":\"USD\"}\n",
425-
"\u001b[32m********************************************************\u001b[0m\n",
430+
"{\n",
431+
" \"base\": {\n",
432+
" \"currency\": \"EUR\",\n",
433+
" \"amount\": 112.23\n",
434+
" },\n",
435+
" \"quote_currency\": \"USD\"\n",
436+
"}\n",
437+
"\u001b[32m************************************************************************************\u001b[0m\n",
426438
"\n",
427439
"--------------------------------------------------------------------------------\n",
428440
"\u001b[35m\n",
@@ -436,7 +448,7 @@
436448
"--------------------------------------------------------------------------------\n",
437449
"\u001b[33mchatbot\u001b[0m (to user_proxy):\n",
438450
"\n",
439-
"112.23 Euros is equivalent to approximately 123.45 US Dollars.\n",
451+
"112.23 Euros is approximately 123.45 US Dollars.\n",
440452
"\n",
441453
"--------------------------------------------------------------------------------\n",
442454
"\u001b[33muser_proxy\u001b[0m (to chatbot):\n",
@@ -477,10 +489,16 @@
477489
"--------------------------------------------------------------------------------\n",
478490
"\u001b[33mchatbot\u001b[0m (to user_proxy):\n",
479491
"\n",
480-
"\u001b[32m***** Suggested function Call: currency_calculator *****\u001b[0m\n",
492+
"\u001b[32m***** Suggested tool Call (call_WrBjnoLeXilBPuj9nTJLM5wh): currency_calculator *****\u001b[0m\n",
481493
"Arguments: \n",
482-
"{\"base\":{\"currency\":\"USD\",\"amount\":123.45},\"quote_currency\":\"EUR\"}\n",
483-
"\u001b[32m********************************************************\u001b[0m\n",
494+
"{\n",
495+
" \"base\": {\n",
496+
" \"currency\": \"USD\",\n",
497+
" \"amount\": 123.45\n",
498+
" },\n",
499+
" \"quote_currency\": \"EUR\"\n",
500+
"}\n",
501+
"\u001b[32m************************************************************************************\u001b[0m\n",
484502
"\n",
485503
"--------------------------------------------------------------------------------\n",
486504
"\u001b[35m\n",
@@ -543,7 +561,7 @@
543561
"name": "python",
544562
"nbconvert_exporter": "python",
545563
"pygments_lexer": "ipython3",
546-
"version": "3.10.13"
564+
"version": "3.11.6"
547565
}
548566
},
549567
"nbformat": 4,

0 commit comments

Comments
 (0)