Add user proxy docs. Make user proxy's default impl cancellable (#4459)

* Add user proxy docs. Make user proxy's default impl cancellable

* remove unnecessary import

* revert accidental change

* address PR comments

* uv sync

* Fix bugs

* poe format

* fixing mypy issues

* poe format

* ignore pyright errors for ainput

* fix example code

* remove unused import

* fix accidental reversion, example code

* formatting

* fix typing

* Update python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/agents.ipynb

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/agents.ipynb

---------

Co-authored-by: Jack Gerrits <jackgerrits@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
This commit is contained in:
peterychang 2024-12-06 17:53:59 -05:00 committed by GitHub
parent 8dac072658
commit c5c3444bce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 114 additions and 2 deletions

View File

@ -2,6 +2,7 @@ import asyncio
from inspect import iscoroutinefunction
from typing import Awaitable, Callable, List, Optional, Sequence, Union, cast
from aioconsole import ainput # type: ignore
from autogen_core import CancellationToken
from ..base import Response
@ -14,6 +15,15 @@ AsyncInputFunc = Callable[[str, Optional[CancellationToken]], Awaitable[str]]
InputFuncType = Union[SyncInputFunc, AsyncInputFunc]
# TODO: ainput doesn't seem to play nicely with jupyter.
# No input window appears in this case.
async def cancellable_input(prompt: str, cancellation_token: Optional[CancellationToken]) -> str:
task = asyncio.Task[str](asyncio.create_task(ainput(prompt))) # type: ignore
if cancellation_token is not None:
cancellation_token.link_future(task)
return await task
class UserProxyAgent(BaseChatAgent):
"""An agent that can represent a human user through an input function.
@ -40,6 +50,63 @@ class UserProxyAgent(BaseChatAgent):
See `Pause for User Input <https://microsoft.github.io/autogen/dev/user-guide/agentchat-user-guide/tutorial/teams.html#pause-for-user-input>`_ for more information.
Example:
Simple usage case::
import asyncio
from autogen_core import CancellationToken
from autogen_agentchat.agents import UserProxyAgent
from autogen_agentchat.messages import TextMessage
async def simple_user_agent():
agent = UserProxyAgent("user_proxy")
response = await asyncio.create_task(
agent.on_messages(
[TextMessage(content="What is your name? ", source="user")],
cancellation_token=CancellationToken(),
)
)
print(f"Your name is {response.chat_message.content}")
Example:
Cancellable usage case::
import asyncio
from typing import Any
from autogen_core import CancellationToken
from autogen_agentchat.agents import UserProxyAgent
from autogen_agentchat.messages import TextMessage
token = CancellationToken()
agent = UserProxyAgent("user_proxy")
async def timeout(delay: float):
await asyncio.sleep(delay)
def cancellation_callback(task: asyncio.Task[Any]):
token.cancel()
async def cancellable_user_agent():
try:
timeout_task = asyncio.create_task(timeout(3))
timeout_task.add_done_callback(cancellation_callback)
agent_task = asyncio.create_task(
agent.on_messages(
[TextMessage(content="What is your name? ", source="user")],
cancellation_token=CancellationToken(),
)
)
response = await agent_task
print(f"Your name is {response.chat_message.content}")
except Exception as e:
print(f"Exception: {e}")
except BaseException as e:
print(f"BaseException: {e}")
"""
def __init__(
@ -51,7 +118,7 @@ class UserProxyAgent(BaseChatAgent):
) -> None:
"""Initialize the UserProxyAgent."""
super().__init__(name=name, description=description)
self.input_func = input_func or input
self.input_func = input_func or cancellable_input
self._is_async = iscoroutinefunction(self.input_func)
@property

View File

@ -26,7 +26,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
@ -101,6 +101,39 @@
"as well as a list of inner messages in the {py:attr}`~autogen_agentchat.base.Response.inner_messages` attribute,\n",
"which stores the agent's \"thought process\" that led to the final response.\n",
"\n",
"## User Proxy Agent\n",
"\n",
"{py:class}`~autogen_agentchat.agents.UserProxyAgent` is a built-in agent that\n",
"provides one way for a user to intervene in the process. This agent will put the team in a temporary blocking state, and thus any exceptions or runtime failures while in the blocked state will result in a deadlock. It is strongly advised that this agent be coupled with a timeout mechanic and that all errors and exceptions emanating from it are handled."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from autogen_agentchat.agents import UserProxyAgent\n",
"\n",
"\n",
"async def user_proxy_run() -> None:\n",
" user_proxy_agent = UserProxyAgent(\"user_proxy\")\n",
" response = await user_proxy_agent.on_messages(\n",
" [TextMessage(content=\"What is your name? \", source=\"user\")], cancellation_token=CancellationToken()\n",
" )\n",
" print(f\"Your name is {response.chat_message.content}\")\n",
"\n",
"\n",
"# Use asyncio.run(user_proxy_run()) when running in a script.\n",
"await user_proxy_run()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The User Proxy agent is ideally used for on-demand human-in-the-loop interactions for scenarios such as Just In Time approvals, human feedback, alerts, etc. For slower user interactions, consider terminating the session using a termination condition and start another one from run or run_stream with another message.\n",
"\n",
"### Stream Messages\n",
"\n",
"We can also stream each message as it is generated by the agent by using the\n",

View File

@ -17,6 +17,7 @@ classifiers = [
dependencies = [
"openai>=1.3",
"pillow",
"aioconsole",
"aiohttp",
"typing-extensions",
"pydantic<3.0.0,>=2.0.0",

View File

@ -111,6 +111,15 @@ dev = [
{ name = "types-tabulate" },
]
[[package]]
name = "aioconsole"
version = "0.8.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c7/c9/c57e979eea211b10a63783882a826f257713fa7c0d6c9a6eac851e674fb4/aioconsole-0.8.1.tar.gz", hash = "sha256:0535ce743ba468fb21a1ba43c9563032c779534d4ecd923a46dbd350ad91d234", size = 61085 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/ea/23e756ec1fea0c685149304dda954b3b3932d6d06afbf42a66a2e6dc2184/aioconsole-0.8.1-py3-none-any.whl", hash = "sha256:e1023685cde35dde909fbf00631ffb2ed1c67fe0b7058ebb0892afbde5f213e5", size = 43324 },
]
[[package]]
name = "aiofiles"
version = "24.1.0"
@ -356,6 +365,7 @@ name = "autogen-core"
version = "0.4.0.dev9"
source = { editable = "packages/autogen-core" }
dependencies = [
{ name = "aioconsole" },
{ name = "aiohttp" },
{ name = "asyncio-atexit" },
{ name = "jsonref" },
@ -419,6 +429,7 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "aioconsole" },
{ name = "aiohttp" },
{ name = "asyncio-atexit" },
{ name = "grpcio", marker = "extra == 'grpc'", specifier = "~=1.62.0" },