chore: 添加虚拟环境到仓库

- 添加 backend_service/venv 虚拟环境
- 包含所有Python依赖包
- 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
2025-12-03 10:19:25 +08:00
parent a6c2027caa
commit c4f851d387
12655 changed files with 3009376 additions and 0 deletions

View File

@@ -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",
]

View File

@@ -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,
)

View File

@@ -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

View File

@@ -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)