mirror of https://github.com/microsoft/autogen.git
remove _ from send/pub, add docs, (#91)
* remove _ from send/pub, add docs, * fixes
This commit is contained in:
parent
5b01f69b58
commit
4cebf7257b
|
@ -54,5 +54,4 @@ html_theme_options = {
|
|||
autodoc_default_options = {
|
||||
"members": True,
|
||||
"undoc-members": True,
|
||||
"private-members": True
|
||||
}
|
|
@ -49,7 +49,7 @@ When an agent receives a message the runtime will invoke the agent's message han
|
|||
|
||||
### Direct Communication
|
||||
|
||||
Direct communication is effectively an RPC call directly to another agent. When sending a direct message to another agent, the receiving agent can respond to the message with another message, or simply return `None`. To send a message to another agent, within a message handler use the {py:meth}`agnext.core.BaseAgent._send_message` method. Awaiting this call will return the response of the invoked agent. If the receiving agent raises an exception, this will be propagated back to the sending agent.
|
||||
Direct communication is effectively an RPC call directly to another agent. When sending a direct message to another agent, the receiving agent can respond to the message with another message, or simply return `None`. To send a message to another agent, within a message handler use the {py:meth}`agnext.core.BaseAgent.send_message` method. Awaiting this call will return the response of the invoked agent. If the receiving agent raises an exception, this will be propagated back to the sending agent.
|
||||
|
||||
To send a message to an agent outside of agent handling a message the message should be sent via the runtime with the {py:meth}`agnext.core.AgentRuntime.send_message` method. This is often how an application might "start" a workflow or conversation.
|
||||
|
||||
|
@ -57,7 +57,7 @@ To send a message to an agent outside of agent handling a message the message sh
|
|||
|
||||
As part of the agent's implementation it must advertise the message types that it would like to receive when published ({py:attr}`agnext.core.Agent.subscriptions`). If one of these messages is published, the agent's message handler will be invoked. The key difference between direct and broadcast communication is that broadcast communication is not a request/response pattern. When an agent publishes a message it is one way, it is not expecting a response from any other agent. In fact, they cannot respond to the message.
|
||||
|
||||
To publish a message to all agents, use the {py:meth}`agnext.core.BaseAgent._publish_message` method. This call must still be awaited to allow the runtime to deliver the message to all agents, but it will always return `None`. If an agent raises an exception while handling a published message, this will be logged but will not be propagated back to the publishing agent.
|
||||
To publish a message to all agents, use the {py:meth}`agnext.core.BaseAgent.publish_message` method. This call must still be awaited to allow the runtime to deliver the message to all agents, but it will always return `None`. If an agent raises an exception while handling a published message, this will be logged but will not be propagated back to the publishing agent.
|
||||
|
||||
To publish a message to all agents outside of an agent handling a message, the message should be published via the runtime with the {py:meth}`agnext.core.AgentRuntime.publish_message` method.
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Namespace
|
||||
|
||||
A namespace is a logical boundary between agents. By default, agents in one
|
||||
namespace cannot communicate with agents in another namespace.
|
||||
|
||||
Namespaces are strings, and the default is `default`.
|
||||
|
||||
Two possible use cases of agents are:
|
||||
|
||||
- Creating a multi-tenant system where each tenant has its own namespace. For
|
||||
example, a chat system where each tenant has its own set of agents.
|
||||
- Security boundaries between agent groups. For example, a chat system where
|
||||
agents in the `admin` namespace can communicate with agents in the `user`
|
||||
namespace, but not the other way around.
|
||||
|
||||
The {py:class}`agnext.core.AgentId` is used to address an agent, it is the combination of the agent's namespace and its name.
|
||||
|
||||
When getting an agent reference ({py:meth}`agnext.core.AgentRuntime.get`) or proxy ({py:meth}`agnext.core.AgentRuntime.get_proxy`) from the runtime the namespace can be specified. Agents have an ID property ({py:attr}`agnext.core.Agent.id`) that returns the agent's id. Additionally, the register method takes a factory that can optionally accept the ID as an argument ({py:meth}`agnext.core.AgentRuntime.register`).
|
|
@ -10,3 +10,27 @@ Further readings:
|
|||
|
||||
1. {py:class}`agnext.core.AgentRuntime`
|
||||
2. {py:class}`agnext.application.SingleThreadedAgentRuntime`
|
||||
|
||||
## Agent Registration
|
||||
|
||||
Agents are registered with the runtime using the
|
||||
{py:meth}`agnext.core.AgentRuntime.register` method. The process of registration
|
||||
associates some name, which is the `type` of the agent with a factory function
|
||||
that is able to create an instance of the agent in a given namespace. The reason
|
||||
for the factory function is to allow automatic creation of agents when they are
|
||||
needed, including automatic creation of agents for not yet existing namespaces.
|
||||
|
||||
Once an agent is registered, a reference to the agent can be retrieved by
|
||||
calling {py:meth}`agnext.core.AgentRuntime.get` or
|
||||
{py:meth}`agnext.core.AgentRuntime.get_proxy`. There is a convenience method
|
||||
{py:meth}`agnext.core.AgentRuntime.register_and_get` that both registers a type
|
||||
and gets a reference.
|
||||
|
||||
A byproduct of this process of `register` + `get` is that
|
||||
{py:class}`agnext.core.Agent` interface is a purely implementation contract. All
|
||||
agents must be communicated with via the runtime. This is a key design decision
|
||||
that allows the runtime to manage the lifecycle of agents, and to provide a
|
||||
consistent API for interacting with agents. Therefore, to communicate with
|
||||
another agent the {py:class}`agnext.core.AgentId` must be used. There is a
|
||||
convenience class {py:meth}`agnext.core.AgentProxy` that bundles an ID and a
|
||||
runtime together.
|
|
@ -38,7 +38,7 @@ class MyAgent(TypeRoutedAgent):
|
|||
self, message: TextMessage | MultiModalMessage, cancellation_token: CancellationToken
|
||||
) -> None:
|
||||
self._received_count += 1
|
||||
await self._publish_message(
|
||||
await self.publish_message(
|
||||
TextMessage(
|
||||
content=f"I received a message from {message.source}. Message received #{self._received_count}",
|
||||
source=self.metadata["name"],
|
||||
|
|
|
@ -29,6 +29,7 @@ common uses, such as chat completion agents, but also allows for fully custom ag
|
|||
core-concepts/tools
|
||||
core-concepts/cancellation
|
||||
core-concepts/logging
|
||||
core-concepts/namespace
|
||||
|
||||
.. toctree::
|
||||
:caption: Guides
|
||||
|
|
|
@ -104,7 +104,7 @@ class UserProxyAgent(TypeRoutedAgent): # type: ignore
|
|||
return
|
||||
else:
|
||||
# Publish user input and exit handler.
|
||||
await self._publish_message(TextMessage(content=user_input, source=self.metadata["name"]))
|
||||
await self.publish_message(TextMessage(content=user_input, source=self.metadata["name"]))
|
||||
return
|
||||
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ Use the following JSON format to provide your thought on the latest message and
|
|||
|
||||
# Publish the response if needed.
|
||||
if respond is True or str(respond).lower().strip() == "true":
|
||||
await self._publish_message(TextMessage(source=self.metadata["name"], content=str(response)))
|
||||
await self.publish_message(TextMessage(source=self.metadata["name"], content=str(response)))
|
||||
|
||||
|
||||
class ChatRoomUserAgent(TextualUserAgent): # type: ignore
|
||||
|
|
|
@ -30,7 +30,7 @@ class Outer(TypeRoutedAgent): # type: ignore
|
|||
|
||||
@message_handler() # type: ignore
|
||||
async def on_new_message(self, message: MessageType, cancellation_token: CancellationToken) -> MessageType: # type: ignore
|
||||
inner_response = self._send_message(message, self._inner)
|
||||
inner_response = self.send_message(message, self._inner)
|
||||
inner_message = await inner_response
|
||||
assert isinstance(inner_message, MessageType)
|
||||
return MessageType(body=f"Outer: {inner_message.body}", sender=self.metadata["name"])
|
||||
|
|
|
@ -114,7 +114,7 @@ class ChatCompletionAgent(TypeRoutedAgent):
|
|||
response = await self._generate_response(message.response_format, cancellation_token)
|
||||
|
||||
# Publish the response.
|
||||
await self._publish_message(response)
|
||||
await self.publish_message(response)
|
||||
|
||||
@message_handler()
|
||||
async def on_tool_call_message(
|
||||
|
@ -190,7 +190,7 @@ class ChatCompletionAgent(TypeRoutedAgent):
|
|||
and all(isinstance(x, FunctionCall) for x in response.content)
|
||||
):
|
||||
# Send a function call message to itself.
|
||||
response = await self._send_message(
|
||||
response = await self.send_message(
|
||||
message=FunctionCallMessage(content=response.content, source=self.metadata["name"]),
|
||||
recipient=self.id,
|
||||
cancellation_token=cancellation_token,
|
||||
|
@ -236,7 +236,7 @@ class ChatCompletionAgent(TypeRoutedAgent):
|
|||
approval_request = ToolApprovalRequest(
|
||||
tool_call=FunctionCall(id=call_id, arguments=json.dumps(args), name=name)
|
||||
)
|
||||
approval_response = await self._send_message(
|
||||
approval_response = await self.send_message(
|
||||
message=approval_request,
|
||||
recipient=self._tool_approver,
|
||||
cancellation_token=cancellation_token,
|
||||
|
|
|
@ -41,7 +41,7 @@ class ImageGenerationAgent(TypeRoutedAgent):
|
|||
@message_handler
|
||||
async def on_publish_now(self, message: PublishNow, cancellation_token: CancellationToken) -> None:
|
||||
response = await self._generate_response(cancellation_token)
|
||||
self._publish_message(response)
|
||||
self.publish_message(response)
|
||||
|
||||
async def _generate_response(self, cancellation_token: CancellationToken) -> MultiModalMessage:
|
||||
messages = await self._memory.get_messages()
|
||||
|
|
|
@ -79,7 +79,7 @@ class OpenAIAssistantAgent(TypeRoutedAgent):
|
|||
async def on_publish_now(self, message: PublishNow, cancellation_token: CancellationToken) -> None:
|
||||
"""Handle a publish now message. This method generates a response and publishes it."""
|
||||
response = await self._generate_response(message.response_format, cancellation_token)
|
||||
await self._publish_message(response)
|
||||
await self.publish_message(response)
|
||||
|
||||
async def _generate_response(
|
||||
self, requested_response_format: ResponseFormat, cancellation_token: CancellationToken
|
||||
|
|
|
@ -24,7 +24,7 @@ class UserProxyAgent(TypeRoutedAgent):
|
|||
async def on_publish_now(self, message: PublishNow, cancellation_token: CancellationToken) -> None:
|
||||
"""Handle a publish now message. This method prompts the user for input, then publishes it."""
|
||||
user_input = await self.get_user_input(self._user_input_prompt)
|
||||
await self._publish_message(TextMessage(content=user_input, source=self.metadata["name"]))
|
||||
await self.publish_message(TextMessage(content=user_input, source=self.metadata["name"]))
|
||||
|
||||
async def get_user_input(self, prompt: str) -> str:
|
||||
"""Get user input from the console. Override this method to customize how user input is retrieved."""
|
||||
|
|
|
@ -141,7 +141,7 @@ class GroupChatManager(TypeRoutedAgent):
|
|||
|
||||
if speaker is not None:
|
||||
# Send the message to the selected speaker to ask it to publish a response.
|
||||
await self._send_message(PublishNow(), speaker)
|
||||
await self.send_message(PublishNow(), speaker)
|
||||
|
||||
def save_state(self) -> Mapping[str, Any]:
|
||||
return {
|
||||
|
|
|
@ -50,7 +50,7 @@ class OrchestratorChat(TypeRoutedAgent):
|
|||
while total_turns < self._max_turns:
|
||||
# Reset all agents.
|
||||
for agent in [*self._specialists, self._orchestrator]:
|
||||
await self._send_message(Reset(), agent)
|
||||
await self.send_message(Reset(), agent)
|
||||
|
||||
# Create the task specs.
|
||||
task_specs = f"""
|
||||
|
@ -72,7 +72,7 @@ Some additional points to consider:
|
|||
|
||||
# Send the task specs to the orchestrator and specialists.
|
||||
for agent in [*self._specialists, self._orchestrator]:
|
||||
await self._send_message(TextMessage(content=task_specs, source=self.metadata["name"]), agent)
|
||||
await self.send_message(TextMessage(content=task_specs, source=self.metadata["name"]), agent)
|
||||
|
||||
# Inner loop.
|
||||
stalled_turns = 0
|
||||
|
@ -126,7 +126,7 @@ Some additional points to consider:
|
|||
|
||||
# Update agents.
|
||||
for agent in [*self._specialists, self._orchestrator]:
|
||||
_ = await self._send_message(
|
||||
_ = await self.send_message(
|
||||
TextMessage(content=subtask, source=self.metadata["name"]),
|
||||
agent,
|
||||
)
|
||||
|
@ -138,12 +138,12 @@ Some additional points to consider:
|
|||
raise ValueError(f"Invalid next speaker: {data['next_speaker']['answer']}") from e
|
||||
|
||||
# Ask speaker to speak.
|
||||
speaker_response = await self._send_message(RespondNow(), speaker)
|
||||
speaker_response = await self.send_message(RespondNow(), speaker)
|
||||
assert speaker_response is not None
|
||||
|
||||
# Update all other agents with the speaker's response.
|
||||
for agent in [agent for agent in self._specialists if agent != speaker] + [self._orchestrator]:
|
||||
await self._send_message(
|
||||
await self.send_message(
|
||||
TextMessage(
|
||||
content=speaker_response.content,
|
||||
source=speaker_response.source,
|
||||
|
@ -161,7 +161,7 @@ Some additional points to consider:
|
|||
|
||||
async def _prepare_task(self, task: str, sender: str) -> Tuple[str, str, str, str]:
|
||||
# Reset planner.
|
||||
await self._send_message(Reset(), self._planner)
|
||||
await self.send_message(Reset(), self._planner)
|
||||
|
||||
# A reusable description of the team.
|
||||
team = "\n".join(
|
||||
|
@ -198,8 +198,8 @@ When answering this survey, keep in mind that "facts" will typically be specific
|
|||
""".strip()
|
||||
|
||||
# Ask the planner to obtain prior knowledge about facts.
|
||||
await self._send_message(TextMessage(content=closed_book_prompt, source=sender), self._planner)
|
||||
facts_response = await self._send_message(RespondNow(), self._planner)
|
||||
await self.send_message(TextMessage(content=closed_book_prompt, source=sender), self._planner)
|
||||
facts_response = await self.send_message(RespondNow(), self._planner)
|
||||
|
||||
facts = str(facts_response.content)
|
||||
|
||||
|
@ -211,8 +211,8 @@ When answering this survey, keep in mind that "facts" will typically be specific
|
|||
Based on the team composition, and known and unknown facts, please devise a short bullet-point plan for addressing the original request. Remember, there is no requirement to involve all team members -- a team member's particular expertise may not be needed for this task.""".strip()
|
||||
|
||||
# Send second messag eto the planner.
|
||||
await self._send_message(TextMessage(content=plan_prompt, source=sender), self._planner)
|
||||
plan_response = await self._send_message(RespondNow(), self._planner)
|
||||
await self.send_message(TextMessage(content=plan_prompt, source=sender), self._planner)
|
||||
plan_response = await self.send_message(RespondNow(), self._planner)
|
||||
plan = str(plan_response.content)
|
||||
|
||||
return team, names, facts, plan
|
||||
|
@ -264,9 +264,9 @@ Please output an answer in pure JSON format according to the following schema. T
|
|||
request = step_prompt
|
||||
while True:
|
||||
# Send a message to the orchestrator.
|
||||
await self._send_message(TextMessage(content=request, source=sender), self._orchestrator)
|
||||
await self.send_message(TextMessage(content=request, source=sender), self._orchestrator)
|
||||
# Request a response.
|
||||
step_response = await self._send_message(
|
||||
step_response = await self.send_message(
|
||||
RespondNow(response_format=ResponseFormat.json_object),
|
||||
self._orchestrator,
|
||||
)
|
||||
|
@ -327,9 +327,9 @@ Please output an answer in pure JSON format according to the following schema. T
|
|||
{facts}
|
||||
""".strip()
|
||||
# Send a message to the orchestrator.
|
||||
await self._send_message(TextMessage(content=new_facts_prompt, source=sender), self._orchestrator)
|
||||
await self.send_message(TextMessage(content=new_facts_prompt, source=sender), self._orchestrator)
|
||||
# Request a response.
|
||||
new_facts_response = await self._send_message(RespondNow(), self._orchestrator)
|
||||
new_facts_response = await self.send_message(RespondNow(), self._orchestrator)
|
||||
return str(new_facts_response.content)
|
||||
|
||||
async def _educated_guess(self, facts: str, sender: str) -> Any:
|
||||
|
@ -352,12 +352,12 @@ Please output an answer in pure JSON format according to the following schema. T
|
|||
request = educated_guess_promt
|
||||
while True:
|
||||
# Send a message to the orchestrator.
|
||||
await self._send_message(
|
||||
await self.send_message(
|
||||
TextMessage(content=request, source=sender),
|
||||
self._orchestrator,
|
||||
)
|
||||
# Request a response.
|
||||
response = await self._send_message(
|
||||
response = await self.send_message(
|
||||
RespondNow(response_format=ResponseFormat.json_object),
|
||||
self._orchestrator,
|
||||
)
|
||||
|
@ -386,7 +386,7 @@ Team membership:
|
|||
{team}
|
||||
""".strip()
|
||||
# Send a message to the orchestrator.
|
||||
await self._send_message(TextMessage(content=new_plan_prompt, source=sender), self._orchestrator)
|
||||
await self.send_message(TextMessage(content=new_plan_prompt, source=sender), self._orchestrator)
|
||||
# Request a response.
|
||||
new_plan_response = await self._send_message(RespondNow(), self._orchestrator)
|
||||
new_plan_response = await self.send_message(RespondNow(), self._orchestrator)
|
||||
return str(new_plan_response.content)
|
||||
|
|
|
@ -5,6 +5,7 @@ from ._model_client import ModelCapabilities
|
|||
# Based on: https://platform.openai.com/docs/models/continuous-model-upgrades
|
||||
# This is a moving target, so correctness is checked by the model value returned by openai against expected values at runtime``
|
||||
_MODEL_POINTERS = {
|
||||
"gpt-4o": "gpt-4o-2024-05-13",
|
||||
"gpt-4-turbo": "gpt-4-turbo-2024-04-09",
|
||||
"gpt-4-turbo-preview": "gpt-4-0125-preview",
|
||||
"gpt-4": "gpt-4-0613",
|
||||
|
@ -14,6 +15,11 @@ _MODEL_POINTERS = {
|
|||
}
|
||||
|
||||
_MODEL_CAPABILITIES: Dict[str, ModelCapabilities] = {
|
||||
"gpt-4o-2024-05-13": {
|
||||
"vision": True,
|
||||
"function_calling": True,
|
||||
"json_output": True,
|
||||
},
|
||||
"gpt-4-turbo-2024-04-09": {
|
||||
"vision": True,
|
||||
"function_calling": True,
|
||||
|
|
|
@ -60,7 +60,31 @@ class AgentRuntime(Protocol):
|
|||
agent_factory: Callable[[], T] | Callable[[AgentRuntime, AgentId], T],
|
||||
*,
|
||||
valid_namespaces: Sequence[str] | Type[AllNamespaces] = AllNamespaces,
|
||||
) -> None: ...
|
||||
) -> None:
|
||||
"""Register an agent factory with the runtime associated with a specific name. The name must be unique.
|
||||
|
||||
Args:
|
||||
name (str): The name of the type agent this factory creates.
|
||||
agent_factory (Callable[[], T] | Callable[[AgentRuntime, AgentId], T]): The factory that creates the agent.
|
||||
valid_namespaces (Sequence[str] | Type[AllNamespaces], optional): Valid namespaces for this type. Defaults to AllNamespaces.
|
||||
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
runtime.register(
|
||||
"chat_agent",
|
||||
lambda: ChatCompletionAgent(
|
||||
description="A generic chat agent.",
|
||||
system_messages=[SystemMessage("You are a helpful assistant")],
|
||||
model_client=OpenAI(model="gpt-4o"),
|
||||
memory=BufferedChatMemory(buffer_size=10),
|
||||
),
|
||||
)
|
||||
|
||||
"""
|
||||
|
||||
...
|
||||
|
||||
def get(self, name: str, *, namespace: str = "default") -> AgentId: ...
|
||||
def get_proxy(self, name: str, *, namespace: str = "default") -> AgentProxy: ...
|
||||
|
|
|
@ -60,7 +60,7 @@ class BaseAgent(ABC, Agent):
|
|||
async def on_message(self, message: Any, cancellation_token: CancellationToken) -> Any: ...
|
||||
|
||||
# Returns the response of the message
|
||||
def _send_message(
|
||||
def send_message(
|
||||
self,
|
||||
message: Any,
|
||||
recipient: AgentId,
|
||||
|
@ -82,7 +82,7 @@ class BaseAgent(ABC, Agent):
|
|||
cancellation_token.link_future(future)
|
||||
return future
|
||||
|
||||
def _publish_message(
|
||||
def publish_message(
|
||||
self,
|
||||
message: Any,
|
||||
*,
|
||||
|
|
|
@ -43,7 +43,7 @@ class NestingLongRunningAgent(TypeRoutedAgent):
|
|||
@message_handler
|
||||
async def on_new_message(self, message: MessageType, cancellation_token: CancellationToken) -> MessageType:
|
||||
self.called = True
|
||||
response = self._send_message(message, self._nested_agent, cancellation_token=cancellation_token)
|
||||
response = self.send_message(message, self._nested_agent, cancellation_token=cancellation_token)
|
||||
try:
|
||||
val = await response
|
||||
assert isinstance(val, MessageType)
|
||||
|
|
Loading…
Reference in New Issue