Skip to content

Function calling upgrade #1443

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 31, 2024
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,6 @@ test/test_files/agenteval-in-out/out/
# Files created by tests
*tmp_code_*
test/agentchat/test_agent_scripts/*

# test cache
.cache_test
7 changes: 4 additions & 3 deletions autogen/agentchat/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from .agent import Agent
from .assistant_agent import AssistantAgent
from .conversable_agent import ConversableAgent
from .conversable_agent import ConversableAgent, register_function
from .groupchat import GroupChat, GroupChatManager
from .user_proxy_agent import UserProxyAgent

__all__ = [
__all__ = (
"Agent",
"ConversableAgent",
"AssistantAgent",
"UserProxyAgent",
"GroupChat",
"GroupChatManager",
]
"register_function",
)
64 changes: 58 additions & 6 deletions autogen/agentchat/conversable_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -914,9 +914,20 @@ def generate_function_call_reply(
func_call = message["function_call"]
func = self._function_map.get(func_call.get("name", None), None)
if inspect.iscoroutinefunction(func):
return False, None

_, func_return = self.execute_function(message["function_call"])
try:
# get the running loop if it was already created
loop = asyncio.get_running_loop()
close_loop = False
except RuntimeError:
# create a loop if there is no running loop
loop = asyncio.new_event_loop()
close_loop = True

_, func_return = loop.run_until_complete(self.a_execute_function(func_call))
if close_loop:
loop.close()
else:
_, func_return = self.execute_function(message["function_call"])
return True, func_return
return False, None

Expand All @@ -943,7 +954,9 @@ async def a_generate_function_call_reply(
func = self._function_map.get(func_name, None)
if func and inspect.iscoroutinefunction(func):
_, func_return = await self.a_execute_function(func_call)
return True, func_return
else:
_, func_return = self.execute_function(func_call)
return True, func_return

return False, None

Expand All @@ -968,8 +981,20 @@ def generate_tool_calls_reply(
function_call = tool_call.get("function", {})
func = self._function_map.get(function_call.get("name", None), None)
if inspect.iscoroutinefunction(func):
continue
_, func_return = self.execute_function(function_call)
try:
# get the running loop if it was already created
loop = asyncio.get_running_loop()
close_loop = False
except RuntimeError:
# create a loop if there is no running loop
loop = asyncio.new_event_loop()
close_loop = True

_, func_return = loop.run_until_complete(self.a_execute_function(function_call))
if close_loop:
loop.close()
else:
_, func_return = self.execute_function(function_call)
tool_returns.append(
{
"tool_call_id": id,
Expand Down Expand Up @@ -1986,3 +2011,30 @@ def get_total_usage(self) -> Union[None, Dict[str, int]]:
return None
else:
return self.client.total_usage_summary


def register_function(
f: Callable[..., Any],
*,
caller: ConversableAgent,
executor: ConversableAgent,
name: Optional[str] = None,
description: str,
) -> None:
"""Register a function to be proposed by an agent and executed for an executor.

This function can be used instead of function decorators `@ConversationAgent.register_for_llm` and
`@ConversationAgent.register_for_execution`.

Args:
f: the function to be registered.
caller: the agent calling the function, typically an instance of ConversableAgent.
executor: the agent executing the function, typically an instance of UserProxy.
name: name of the function. If None, the function name will be used (default: None).
description: description of the function. The description is used by LLM to decode whether the function
is called. Make sure the description is properly describing what the function does or it might not be
called by LLM when needed.

"""
f = caller.register_for_llm(name=name, description=description)(f)
executor.register_for_execution(name=name)(f)
213 changes: 136 additions & 77 deletions notebook/agentchat_function_call.ipynb

Large diffs are not rendered by default.

Loading