chore: 添加虚拟环境到仓库
- 添加 backend_service/venv 虚拟环境 - 包含所有Python依赖包 - 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""The pipeline module in AgentScope, that provides syntactic sugar for
|
||||
complex workflows and multi-agent conversations."""
|
||||
|
||||
from ._msghub import MsgHub
|
||||
from ._class import SequentialPipeline, FanoutPipeline
|
||||
from ._functional import (
|
||||
sequential_pipeline,
|
||||
fanout_pipeline,
|
||||
stream_printing_messages,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"MsgHub",
|
||||
"SequentialPipeline",
|
||||
"sequential_pipeline",
|
||||
"FanoutPipeline",
|
||||
"fanout_pipeline",
|
||||
"stream_printing_messages",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,90 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Pipeline classes."""
|
||||
from typing import Any
|
||||
|
||||
from ._functional import sequential_pipeline, fanout_pipeline
|
||||
from ..agent import AgentBase
|
||||
from ..message import Msg
|
||||
|
||||
|
||||
class SequentialPipeline:
|
||||
"""An async sequential pipeline class, which executes a sequence of
|
||||
agents sequentially. Compared with functional pipeline, this class
|
||||
can be re-used."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
agents: list[AgentBase],
|
||||
) -> None:
|
||||
"""Initialize a sequential pipeline class
|
||||
|
||||
Args:
|
||||
agents (`list[AgentBase]`):
|
||||
A list of agents.
|
||||
"""
|
||||
self.agents = agents
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
msg: Msg | list[Msg] | None = None,
|
||||
) -> Msg | list[Msg] | None:
|
||||
"""Execute the sequential pipeline
|
||||
|
||||
Args:
|
||||
msg (`Msg | list[Msg] | None`, defaults to `None`):
|
||||
The initial input that will be passed to the first agent.
|
||||
"""
|
||||
return await sequential_pipeline(
|
||||
agents=self.agents,
|
||||
msg=msg,
|
||||
)
|
||||
|
||||
|
||||
class FanoutPipeline:
|
||||
"""An async fanout pipeline class, which distributes the same input to
|
||||
multiple agents. Compared with functional pipeline, this class can be
|
||||
re-used and configured with default parameters."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
agents: list[AgentBase],
|
||||
enable_gather: bool = True,
|
||||
) -> None:
|
||||
"""Initialize a fanout pipeline class
|
||||
|
||||
Args:
|
||||
agents (`list[AgentBase]`):
|
||||
A list of agents to execute.
|
||||
enable_gather (`bool`, defaults to `True`):
|
||||
Whether to execute agents concurrently
|
||||
using `asyncio.gather()`. If False, agents are executed
|
||||
sequentially.
|
||||
"""
|
||||
self.agents = agents
|
||||
self.enable_gather = enable_gather
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
msg: Msg | list[Msg] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> list[Msg]:
|
||||
"""Execute the fanout pipeline
|
||||
|
||||
Args:
|
||||
msg (`Msg | list[Msg] | None`, defaults to `None`):
|
||||
The input message that will be distributed to all agents.
|
||||
**kwargs (`Any`):
|
||||
Additional keyword arguments passed to each agent during
|
||||
execution.
|
||||
|
||||
Returns:
|
||||
`list[Msg]`:
|
||||
A list of output messages from all agents.
|
||||
"""
|
||||
|
||||
return await fanout_pipeline(
|
||||
agents=self.agents,
|
||||
msg=msg,
|
||||
enable_gather=self.enable_gather,
|
||||
**kwargs,
|
||||
)
|
||||
@@ -0,0 +1,169 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Functional counterpart for Pipeline"""
|
||||
import asyncio
|
||||
from copy import deepcopy
|
||||
from typing import Any, AsyncGenerator, Tuple, Coroutine
|
||||
from ..agent import AgentBase
|
||||
from ..message import Msg
|
||||
|
||||
|
||||
async def sequential_pipeline(
|
||||
agents: list[AgentBase],
|
||||
msg: Msg | list[Msg] | None = None,
|
||||
) -> Msg | list[Msg] | None:
|
||||
"""An async syntactic sugar pipeline that executes a sequence of agents
|
||||
sequentially. The output of the previous agent will be passed as the
|
||||
input to the next agent. The final output will be the output of the
|
||||
last agent.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
agent1 = ReActAgent(...)
|
||||
agent2 = ReActAgent(...)
|
||||
agent3 = ReActAgent(...)
|
||||
|
||||
msg_input = Msg("user", "Hello", "user")
|
||||
|
||||
msg_output = await sequential_pipeline(
|
||||
[agent1, agent2, agent3],
|
||||
msg_input
|
||||
)
|
||||
|
||||
Args:
|
||||
agents (`list[AgentBase]`):
|
||||
A list of agents.
|
||||
msg (`Msg | list[Msg] | None`, defaults to `None`):
|
||||
The initial input that will be passed to the first agent.
|
||||
Returns:
|
||||
`Msg | list[Msg] | None`:
|
||||
The output of the last agent in the sequence.
|
||||
"""
|
||||
for agent in agents:
|
||||
msg = await agent(msg)
|
||||
return msg
|
||||
|
||||
|
||||
async def fanout_pipeline(
|
||||
agents: list[AgentBase],
|
||||
msg: Msg | list[Msg] | None = None,
|
||||
enable_gather: bool = True,
|
||||
**kwargs: Any,
|
||||
) -> list[Msg]:
|
||||
"""A fanout pipeline that distributes the same input to multiple agents.
|
||||
This pipeline sends the same message (or a deep copy of it) to all agents
|
||||
and collects their responses. Agents can be executed either concurrently
|
||||
using asyncio.gather() or sequentially depending on the enable_gather
|
||||
parameter.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
agent1 = ReActAgent(...)
|
||||
agent2 = ReActAgent(...)
|
||||
agent3 = ReActAgent(...)
|
||||
|
||||
msg_input = Msg("user", "Hello", "user")
|
||||
|
||||
# Concurrent execution (default)
|
||||
results = await fanout_pipeline(
|
||||
[agent1, agent2, agent3],
|
||||
msg_input
|
||||
)
|
||||
|
||||
# Sequential execution
|
||||
results = await fanout_pipeline(
|
||||
[agent1, agent2, agent3],
|
||||
msg_input,
|
||||
enable_gather=False
|
||||
)
|
||||
|
||||
Args:
|
||||
agents (`list[AgentBase]`):
|
||||
A list of agents.
|
||||
msg (`Msg | list[Msg] | None`, defaults to `None`):
|
||||
The initial input that will be passed to all agents.
|
||||
enable_gather (`bool`, defaults to `True`):
|
||||
Whether to execute agents concurrently using `asyncio.gather()`.
|
||||
If False, agents are executed sequentially.
|
||||
**kwargs (`Any`):
|
||||
Additional keyword arguments passed to each agent during execution.
|
||||
|
||||
Returns:
|
||||
`list[Msg]`:
|
||||
A list of response messages from each agent.
|
||||
"""
|
||||
if enable_gather:
|
||||
tasks = [
|
||||
asyncio.create_task(agent(deepcopy(msg), **kwargs))
|
||||
for agent in agents
|
||||
]
|
||||
|
||||
return await asyncio.gather(*tasks)
|
||||
else:
|
||||
return [await agent(deepcopy(msg), **kwargs) for agent in agents]
|
||||
|
||||
|
||||
async def stream_printing_messages(
|
||||
agents: list[AgentBase],
|
||||
coroutine_task: Coroutine,
|
||||
end_signal: str = "[END]",
|
||||
) -> AsyncGenerator[Tuple[Msg, bool], None]:
|
||||
"""This pipeline will gather the printing messages from agents when
|
||||
execute the given coroutine task, and yield them one by one.
|
||||
Only the messages that are printed by `await self.print(msg)` in the agent
|
||||
will be forwarded to the message queue and yielded by this pipeline.
|
||||
|
||||
.. note:: The boolean in the yielded tuple indicates whether the message
|
||||
is the last **chunk** for a streaming message, not the last message
|
||||
returned by the agent. That means, there'll be multiple tuples with
|
||||
`is_last_chunk=True` if the agent prints multiple messages.
|
||||
|
||||
.. note:: The messages with the same ``id`` is considered as the same
|
||||
message, e.g., the chunks of a streaming message.
|
||||
|
||||
Args:
|
||||
agents (`list[AgentBase]`):
|
||||
A list of agents whose printing messages will be gathered and
|
||||
yielded.
|
||||
coroutine_task (`Coroutine`):
|
||||
The coroutine task to be executed. This task should involve the
|
||||
execution of the provided agents, so that their printing messages
|
||||
can be captured and yielded.
|
||||
end_signal (`str`, defaults to `"[END]"`):
|
||||
A special signal to indicate the end of message streaming. When
|
||||
this signal is received from the message queue, the generator will
|
||||
stop yielding messages and exit the loop.
|
||||
|
||||
Returns:
|
||||
`AsyncGenerator[Tuple[Msg, bool], None]`:
|
||||
An async generator that yields tuples of (message, is_last_chunk).
|
||||
The `is_last_chunk` boolean indicates whether the message is the
|
||||
last chunk in a streaming message.
|
||||
"""
|
||||
|
||||
# Enable the message queue to get the intermediate messages
|
||||
queue = asyncio.Queue()
|
||||
for agent in agents:
|
||||
# Use one queue to gather messages from all agents
|
||||
agent.set_msg_queue_enabled(True, queue)
|
||||
|
||||
# Execute the agent asynchronously
|
||||
task = asyncio.create_task(coroutine_task)
|
||||
|
||||
if task.done():
|
||||
await queue.put(end_signal)
|
||||
else:
|
||||
task.add_done_callback(lambda _: queue.put_nowait(end_signal))
|
||||
|
||||
# Receive the messages from the agent's message queue
|
||||
while True:
|
||||
# The message obj, and a boolean indicating whether it's the last chunk
|
||||
# in a streaming message
|
||||
printing_msg = await queue.get()
|
||||
|
||||
# End the loop when the message is None
|
||||
if isinstance(printing_msg, str) and printing_msg == end_signal:
|
||||
break
|
||||
|
||||
yield printing_msg
|
||||
@@ -0,0 +1,154 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""MsgHub is designed to share messages among a group of agents."""
|
||||
from typing import Any
|
||||
|
||||
import shortuuid
|
||||
|
||||
from .._logging import logger
|
||||
|
||||
from ..agent import AgentBase
|
||||
from ..message import Msg
|
||||
|
||||
|
||||
class MsgHub:
|
||||
"""MsgHub class that controls the subscription of the participated agents.
|
||||
|
||||
Example:
|
||||
In the following example, the reply message from `agent1`, `agent2`,
|
||||
and `agent3` will be broadcasted to all the other agents in the MsgHub.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with MsgHub(participant=[agent1, agent2, agent3]):
|
||||
agent1()
|
||||
agent2()
|
||||
|
||||
Actually, it has the same effect as the following code, but much more
|
||||
easy and elegant!
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
x1 = agent1()
|
||||
agent2.observe(x1)
|
||||
agent3.observe(x1)
|
||||
|
||||
x2 = agent2()
|
||||
agent1.observe(x2)
|
||||
agent3.observe(x2)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
participants: list[AgentBase],
|
||||
announcement: list[Msg] | Msg | None = None,
|
||||
enable_auto_broadcast: bool = True,
|
||||
name: str | None = None,
|
||||
) -> None:
|
||||
"""Initialize a MsgHub context manager.
|
||||
|
||||
Args:
|
||||
participants (`list[AgentBase]`):
|
||||
A list of agents that participate in the MsgHub.
|
||||
announcement (`list[Msg] | Msg | None`):
|
||||
The message that will be broadcast to all participants when
|
||||
entering the MsgHub.
|
||||
enable_auto_broadcast (`bool`, defaults to `True`):
|
||||
Whether to enable automatic broadcasting of the replied
|
||||
message from any participant to all other participants. If
|
||||
disabled, the MsgHub will only serve as a manual message
|
||||
broadcaster with the `announcement` argument and the
|
||||
`broadcast()` method.
|
||||
name (`str | None`):
|
||||
The name of this MsgHub. If not provided, a random ID
|
||||
will be generated.
|
||||
"""
|
||||
self.name = name or shortuuid.uuid()
|
||||
self.participants = participants
|
||||
self.announcement = announcement
|
||||
self.enable_auto_broadcast = enable_auto_broadcast
|
||||
|
||||
async def __aenter__(self) -> "MsgHub":
|
||||
"""Will be called when entering the MsgHub."""
|
||||
self._reset_subscriber()
|
||||
|
||||
# broadcast the input message to all participants
|
||||
if self.announcement is not None:
|
||||
await self.broadcast(msg=self.announcement)
|
||||
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""Will be called when exiting the MsgHub."""
|
||||
if self.enable_auto_broadcast:
|
||||
for agent in self.participants:
|
||||
agent.remove_subscribers(self.name)
|
||||
|
||||
def _reset_subscriber(self) -> None:
|
||||
"""Reset the subscriber for agent in `self.participant`"""
|
||||
if self.enable_auto_broadcast:
|
||||
for agent in self.participants:
|
||||
agent.reset_subscribers(self.name, self.participants)
|
||||
|
||||
def add(
|
||||
self,
|
||||
new_participant: list[AgentBase] | AgentBase,
|
||||
) -> None:
|
||||
"""Add new participant into this hub"""
|
||||
if isinstance(new_participant, AgentBase):
|
||||
new_participant = [new_participant]
|
||||
|
||||
for agent in new_participant:
|
||||
if agent not in self.participants:
|
||||
self.participants.append(agent)
|
||||
|
||||
self._reset_subscriber()
|
||||
|
||||
def delete(
|
||||
self,
|
||||
participant: list[AgentBase] | AgentBase,
|
||||
) -> None:
|
||||
"""Delete agents from participant."""
|
||||
if isinstance(participant, AgentBase):
|
||||
participant = [participant]
|
||||
|
||||
for agent in participant:
|
||||
if agent in self.participants:
|
||||
# remove agent from self.participant
|
||||
self.participants.pop(self.participants.index(agent))
|
||||
else:
|
||||
logger.warning(
|
||||
"Cannot find the agent with ID %s, skip its deletion.",
|
||||
agent.id,
|
||||
)
|
||||
|
||||
# Remove this agent from the subscriber of other agents
|
||||
self._reset_subscriber()
|
||||
|
||||
async def broadcast(self, msg: list[Msg] | Msg) -> None:
|
||||
"""Broadcast the message to all participants.
|
||||
|
||||
Args:
|
||||
msg (`list[Msg] | Msg`):
|
||||
Message(s) to be broadcast among all participants.
|
||||
"""
|
||||
for agent in self.participants:
|
||||
await agent.observe(msg)
|
||||
|
||||
def set_auto_broadcast(self, enable: bool) -> None:
|
||||
"""Enable automatic broadcasting of the replied message from any
|
||||
participant to all other participants.
|
||||
|
||||
Args:
|
||||
enable (`bool`):
|
||||
Whether to enable automatic broadcasting. If disabled, the
|
||||
MsgHub will only serve as a manual message broadcaster with
|
||||
the `announcement` argument and the `broadcast()` method.
|
||||
"""
|
||||
if enable:
|
||||
self.enable_auto_broadcast = True
|
||||
self._reset_subscriber()
|
||||
else:
|
||||
self.enable_auto_broadcast = False
|
||||
for agent in self.participants:
|
||||
agent.remove_subscribers(self.name)
|
||||
Reference in New Issue
Block a user