init
This commit is contained in:
25
AlgoriAgent/src/agentscope/agents/__init__.py
Normal file
25
AlgoriAgent/src/agentscope/agents/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Import all agent related modules in the package. """
|
||||
from .agent import AgentBase, DistConf
|
||||
from .operator import Operator
|
||||
from .dialog_agent import DialogAgent
|
||||
from .dict_dialog_agent import DictDialogAgent
|
||||
from .user_agent import UserAgent
|
||||
from .text_to_image_agent import TextToImageAgent
|
||||
from .rpc_agent import RpcAgent
|
||||
from .react_agent import ReActAgent
|
||||
from .rag_agent import LlamaIndexAgent
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AgentBase",
|
||||
"Operator",
|
||||
"DialogAgent",
|
||||
"DictDialogAgent",
|
||||
"TextToImageAgent",
|
||||
"UserAgent",
|
||||
"ReActAgent",
|
||||
"DistConf",
|
||||
"RpcAgent",
|
||||
"LlamaIndexAgent",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
458
AlgoriAgent/src/agentscope/agents/agent.py
Normal file
458
AlgoriAgent/src/agentscope/agents/agent.py
Normal file
@@ -0,0 +1,458 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Base class for Agent """
|
||||
|
||||
from __future__ import annotations
|
||||
from abc import ABCMeta
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Union
|
||||
from typing import Any
|
||||
from typing import Type
|
||||
import uuid
|
||||
from loguru import logger
|
||||
|
||||
from agentscope.agents.operator import Operator
|
||||
from agentscope.message import Msg
|
||||
from agentscope.models import load_model_by_config_name
|
||||
from agentscope.memory import TemporaryMemory
|
||||
|
||||
|
||||
class _AgentMeta(ABCMeta):
|
||||
"""The meta-class for agent.
|
||||
|
||||
1. record the init args into `_init_settings` field.
|
||||
2. register class name into `registry` field.
|
||||
"""
|
||||
|
||||
def __init__(cls, name: Any, bases: Any, attrs: Any) -> None:
|
||||
if not hasattr(cls, "_registry"):
|
||||
cls._registry = {}
|
||||
else:
|
||||
if name in cls._registry:
|
||||
logger.warning(
|
||||
f"Agent class with name [{name}] already exists.",
|
||||
)
|
||||
else:
|
||||
cls._registry[name] = cls
|
||||
super().__init__(name, bases, attrs)
|
||||
|
||||
def __call__(cls, *args: tuple, **kwargs: dict) -> Any:
|
||||
to_dist = kwargs.pop("to_dist", False)
|
||||
if to_dist is True:
|
||||
to_dist = DistConf()
|
||||
if to_dist is not False and to_dist is not None:
|
||||
from .rpc_agent import RpcAgent
|
||||
|
||||
if cls is not RpcAgent and not issubclass(cls, RpcAgent):
|
||||
return RpcAgent(
|
||||
name=(
|
||||
args[0]
|
||||
if len(args) > 0
|
||||
else kwargs["name"] # type: ignore[arg-type]
|
||||
),
|
||||
host=to_dist.pop( # type: ignore[arg-type]
|
||||
"host",
|
||||
"localhost",
|
||||
),
|
||||
port=to_dist.pop("port", None), # type: ignore[arg-type]
|
||||
max_pool_size=kwargs.pop( # type: ignore[arg-type]
|
||||
"max_pool_size",
|
||||
8192,
|
||||
),
|
||||
max_timeout_seconds=to_dist.pop( # type: ignore[arg-type]
|
||||
"max_timeout_seconds",
|
||||
1800,
|
||||
),
|
||||
local_mode=to_dist.pop( # type: ignore[arg-type]
|
||||
"local_mode",
|
||||
True,
|
||||
),
|
||||
lazy_launch=to_dist.pop( # type: ignore[arg-type]
|
||||
"lazy_launch",
|
||||
True,
|
||||
),
|
||||
agent_id=cls.generate_agent_id(),
|
||||
connect_existing=False,
|
||||
agent_class=cls,
|
||||
agent_configs={
|
||||
"args": args,
|
||||
"kwargs": kwargs,
|
||||
"class_name": cls.__name__,
|
||||
},
|
||||
)
|
||||
instance = super().__call__(*args, **kwargs)
|
||||
instance._init_settings = {
|
||||
"args": args,
|
||||
"kwargs": kwargs,
|
||||
"class_name": cls.__name__,
|
||||
}
|
||||
return instance
|
||||
|
||||
|
||||
class DistConf(dict):
|
||||
"""Distribution configuration for agents."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str = "localhost",
|
||||
port: int = None,
|
||||
max_pool_size: int = 8192,
|
||||
max_timeout_seconds: int = 1800,
|
||||
local_mode: bool = True,
|
||||
lazy_launch: bool = True,
|
||||
):
|
||||
"""Init the distributed configuration.
|
||||
|
||||
Args:
|
||||
host (`str`, defaults to `"localhost"`):
|
||||
Hostname of the rpc agent server.
|
||||
port (`int`, defaults to `None`):
|
||||
Port of the rpc agent server.
|
||||
max_pool_size (`int`, defaults to `8192`):
|
||||
Max number of task results that the server can accommodate.
|
||||
max_timeout_seconds (`int`, defaults to `1800`):
|
||||
Timeout for task results.
|
||||
local_mode (`bool`, defaults to `True`):
|
||||
Whether the started rpc server only listens to local
|
||||
requests.
|
||||
lazy_launch (`bool`, defaults to `True`):
|
||||
Only launch the server when the agent is called.
|
||||
"""
|
||||
self["host"] = host
|
||||
self["port"] = port
|
||||
self["max_pool_size"] = max_pool_size
|
||||
self["max_timeout_seconds"] = max_timeout_seconds
|
||||
self["local_mode"] = local_mode
|
||||
self["lazy_launch"] = lazy_launch
|
||||
|
||||
|
||||
class AgentBase(Operator, metaclass=_AgentMeta):
|
||||
"""Base class for all agents.
|
||||
|
||||
All agents should inherit from this class and implement the `reply`
|
||||
function.
|
||||
"""
|
||||
|
||||
_version: int = 1
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
sys_prompt: Optional[str] = None,
|
||||
model_config_name: str = None,
|
||||
use_memory: bool = True,
|
||||
memory_config: Optional[dict] = None,
|
||||
to_dist: Optional[Union[DistConf, bool]] = False,
|
||||
) -> None:
|
||||
r"""Initialize an agent from the given arguments.
|
||||
|
||||
Args:
|
||||
name (`str`):
|
||||
The name of the agent.
|
||||
sys_prompt (`Optional[str]`):
|
||||
The system prompt of the agent, which can be passed by args
|
||||
or hard-coded in the agent.
|
||||
model_config_name (`str`, defaults to None):
|
||||
The name of the model config, which is used to load model from
|
||||
configuration.
|
||||
use_memory (`bool`, defaults to `True`):
|
||||
Whether the agent has memory.
|
||||
memory_config (`Optional[dict]`):
|
||||
The config of memory.
|
||||
to_dist (`Optional[Union[DistConf, bool]]`, default to `False`):
|
||||
The configurations passed to :py:meth:`to_dist` method. Used in
|
||||
:py:class:`_AgentMeta`, when this parameter is provided,
|
||||
the agent will automatically be converted into its distributed
|
||||
version. Below are some examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# run as a sub process
|
||||
agent = XXXAgent(
|
||||
# ... other parameters
|
||||
to_dist=True,
|
||||
)
|
||||
|
||||
# connect to an existing agent server
|
||||
agent = XXXAgent(
|
||||
# ... other parameters
|
||||
to_dist=DistConf(
|
||||
host="<ip of your server>",
|
||||
port=<port of your server>,
|
||||
# other parameters
|
||||
),
|
||||
)
|
||||
|
||||
See :doc:`Tutorial<tutorial/208-distribute>` for detail.
|
||||
"""
|
||||
self.name = name
|
||||
self.memory_config = memory_config
|
||||
|
||||
if sys_prompt is not None:
|
||||
self.sys_prompt = sys_prompt
|
||||
|
||||
# TODO: support to receive a ModelWrapper instance
|
||||
if model_config_name is not None:
|
||||
self.model = load_model_by_config_name(model_config_name)
|
||||
|
||||
if use_memory:
|
||||
self.memory = TemporaryMemory(memory_config)
|
||||
else:
|
||||
self.memory = None
|
||||
|
||||
# The global unique id of this agent
|
||||
self._agent_id = self.__class__.generate_agent_id()
|
||||
|
||||
# The audience of this agent, which means if this agent generates a
|
||||
# response, it will be passed to all agents in the audience.
|
||||
self._audience = None
|
||||
# convert to distributed agent, conversion is in `_AgentMeta`
|
||||
if to_dist is not False and to_dist is not None:
|
||||
logger.info(
|
||||
f"Convert {self.__class__.__name__}[{self.name}] into"
|
||||
" a distributed agent.",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def generate_agent_id(cls) -> str:
|
||||
"""Generate the agent_id of this agent instance"""
|
||||
# TODO: change cls.__name__ into a global unique agent_type
|
||||
return f"{cls.__name__}_{uuid.uuid4().hex}"
|
||||
|
||||
# todo: add a unique agent_type field to distinguish different agent class
|
||||
@classmethod
|
||||
def get_agent_class(cls, agent_class_name: str) -> Type[AgentBase]:
|
||||
"""Get the agent class based on the specific agent class name.
|
||||
|
||||
Args:
|
||||
agent_class_name (`str`): the name of the agent class.
|
||||
|
||||
Raises:
|
||||
ValueError: Agent class name not exits.
|
||||
|
||||
Returns:
|
||||
Type[AgentBase]: the AgentBase sub-class.
|
||||
"""
|
||||
if agent_class_name not in cls._registry:
|
||||
raise ValueError(f"Agent [{agent_class_name}] not found.")
|
||||
return cls._registry[agent_class_name] # type: ignore[return-value]
|
||||
|
||||
@classmethod
|
||||
def register_agent_class(cls, agent_class: Type[AgentBase]) -> None:
|
||||
"""Register the agent class into the registry.
|
||||
|
||||
Args:
|
||||
agent_class (Type[AgentBase]): the agent class to be registered.
|
||||
"""
|
||||
agent_class_name = agent_class.__name__
|
||||
if agent_class_name in cls._registry:
|
||||
logger.info(
|
||||
f"Agent class with name [{agent_class_name}] already exists.",
|
||||
)
|
||||
else:
|
||||
cls._registry[agent_class_name] = agent_class
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None, user_backboard:str = "") -> Msg:
|
||||
"""Define the actions taken by this agent.
|
||||
|
||||
Args:
|
||||
x (`Optional[Union[Msg, Sequence[Msg]]]`, defaults to `None`):
|
||||
The input message(s) to the agent, which also can be omitted if
|
||||
the agent doesn't need any input.
|
||||
user_backboard (`str`): The user backboard, including user's IDE files in brief.
|
||||
|
||||
Returns:
|
||||
`Msg`: The output message generated by the agent.
|
||||
|
||||
Note:
|
||||
Given that some agents are in an adversarial environment,
|
||||
their input doesn't include the thoughts of other agents.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
f"Agent [{type(self).__name__}] is missing the required "
|
||||
f'"reply" function.',
|
||||
)
|
||||
|
||||
def load_from_config(self, config: dict) -> None:
|
||||
"""Load configuration for this agent.
|
||||
|
||||
Args:
|
||||
config (`dict`): model configuration
|
||||
"""
|
||||
|
||||
def export_config(self) -> dict:
|
||||
"""Return configuration of this agent.
|
||||
|
||||
Returns:
|
||||
The configuration of current agent.
|
||||
"""
|
||||
return {}
|
||||
|
||||
def load_memory(self, memory: Sequence[dict]) -> None:
|
||||
r"""Load input memory."""
|
||||
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> dict:
|
||||
"""Calling the reply function, and broadcast the generated
|
||||
response to all audiences if needed."""
|
||||
res = self.reply(*args, **kwargs)
|
||||
|
||||
# broadcast to audiences if needed
|
||||
if self._audience is not None:
|
||||
self._broadcast_to_audience(res)
|
||||
|
||||
return res
|
||||
|
||||
def speak(
|
||||
self,
|
||||
content: Union[str, Msg],
|
||||
) -> None:
|
||||
"""
|
||||
Speak out the message generated by the agent. If a string is given,
|
||||
a Msg object will be created with the string as the content.
|
||||
|
||||
Args:
|
||||
content (`Union[str, Msg]`):
|
||||
The content of the message to be spoken out. If a string is
|
||||
given, a Msg object will be created with the agent's name, role
|
||||
as "assistant", and the given string as the content.
|
||||
"""
|
||||
if isinstance(content, str):
|
||||
msg = Msg(
|
||||
name=self.name,
|
||||
content=content,
|
||||
role="assistant",
|
||||
)
|
||||
elif isinstance(content, Msg):
|
||||
msg = content
|
||||
else:
|
||||
raise TypeError(
|
||||
"From version 0.0.5, the speak method only accepts str or Msg "
|
||||
f"object, got {type(content)} instead.",
|
||||
)
|
||||
|
||||
logger.chat(msg)
|
||||
|
||||
def observe(self, x: Union[dict, Sequence[dict]]) -> None:
|
||||
"""Observe the input, store it in memory without response to it.
|
||||
|
||||
Args:
|
||||
x (`Union[dict, Sequence[dict]]`):
|
||||
The input message to be recorded in memory.
|
||||
"""
|
||||
if self.memory:
|
||||
self.memory.add(x)
|
||||
|
||||
def reset_audience(self, audience: Sequence[AgentBase]) -> None:
|
||||
"""Set the audience of this agent, which means if this agent
|
||||
generates a response, it will be passed to all audiences.
|
||||
|
||||
Args:
|
||||
audience (`Sequence[AgentBase]`):
|
||||
The audience of this agent, which will be notified when this
|
||||
agent generates a response message.
|
||||
"""
|
||||
# TODO: we leave the consideration of nested msghub for future.
|
||||
# for now we suppose one agent can only be in one msghub
|
||||
self._audience = [_ for _ in audience if _ != self]
|
||||
|
||||
def clear_audience(self) -> None:
|
||||
"""Remove the audience of this agent."""
|
||||
# TODO: we leave the consideration of nested msghub for future.
|
||||
# for now we suppose one agent can only be in one msghub
|
||||
self._audience = None
|
||||
|
||||
def rm_audience(
|
||||
self,
|
||||
audience: Union[Sequence[AgentBase], AgentBase],
|
||||
) -> None:
|
||||
"""Remove the given audience from the Sequence"""
|
||||
if not isinstance(audience, Sequence):
|
||||
audience = [audience]
|
||||
|
||||
for agent in audience:
|
||||
if self._audience is not None and agent in self._audience:
|
||||
self._audience.pop(self._audience.index(agent))
|
||||
else:
|
||||
logger.warning(
|
||||
f"Skip removing agent [{agent.name}] from the "
|
||||
f"audience for its inexistence.",
|
||||
)
|
||||
|
||||
def _broadcast_to_audience(self, x: dict) -> None:
|
||||
"""Broadcast the input to all audiences."""
|
||||
for agent in self._audience:
|
||||
agent.observe(x)
|
||||
|
||||
@property
|
||||
def agent_id(self) -> str:
|
||||
"""The unique id of this agent.
|
||||
|
||||
Returns:
|
||||
str: agent_id
|
||||
"""
|
||||
return self._agent_id
|
||||
|
||||
def to_dist(
|
||||
self,
|
||||
host: str = "localhost",
|
||||
port: int = None,
|
||||
max_pool_size: int = 8192,
|
||||
max_timeout_seconds: int = 1800,
|
||||
local_mode: bool = True,
|
||||
lazy_launch: bool = True,
|
||||
launch_server: bool = None,
|
||||
) -> AgentBase:
|
||||
"""Convert current agent instance into a distributed version.
|
||||
|
||||
Args:
|
||||
host (`str`, defaults to `"localhost"`):
|
||||
Hostname of the rpc agent server.
|
||||
port (`int`, defaults to `None`):
|
||||
Port of the rpc agent server.
|
||||
max_pool_size (`int`, defaults to `8192`):
|
||||
Only takes effect when `host` and `port` are not filled in.
|
||||
The max number of agent reply messages that the started agent
|
||||
server can accommodate. Note that the oldest message will be
|
||||
deleted after exceeding the pool size.
|
||||
max_timeout_seconds (`int`, defaults to `1800`):
|
||||
Only takes effect when `host` and `port` are not filled in.
|
||||
Maximum time for reply messages to be cached in the launched
|
||||
agent server. Note that expired messages will be deleted.
|
||||
local_mode (`bool`, defaults to `True`):
|
||||
Only takes effect when `host` and `port` are not filled in.
|
||||
Whether the started agent server only listens to local
|
||||
requests.
|
||||
lazy_launch (`bool`, defaults to `True`):
|
||||
Only takes effect when `host` and `port` are not filled in.
|
||||
If `True`, launch the agent server when the agent is called,
|
||||
otherwise, launch the agent server immediately.
|
||||
launch_server(`bool`, defaults to `None`):
|
||||
This field has been deprecated and will be removed in
|
||||
future releases.
|
||||
|
||||
Returns:
|
||||
`AgentBase`: the wrapped agent instance with distributed
|
||||
functionality
|
||||
"""
|
||||
from .rpc_agent import RpcAgent
|
||||
|
||||
if issubclass(self.__class__, RpcAgent):
|
||||
return self
|
||||
if launch_server is not None:
|
||||
logger.warning(
|
||||
"`launch_server` has been deprecated and will be removed in "
|
||||
"future releases. When `host` and `port` is not provided, the "
|
||||
"agent server will be launched automatically.",
|
||||
)
|
||||
return RpcAgent(
|
||||
name=self.name,
|
||||
agent_class=self.__class__,
|
||||
agent_configs=self._init_settings,
|
||||
host=host,
|
||||
port=port,
|
||||
max_pool_size=max_pool_size,
|
||||
max_timeout_seconds=max_timeout_seconds,
|
||||
local_mode=local_mode,
|
||||
lazy_launch=lazy_launch,
|
||||
agent_id=self.agent_id,
|
||||
)
|
||||
82
AlgoriAgent/src/agentscope/agents/dialog_agent.py
Normal file
82
AlgoriAgent/src/agentscope/agents/dialog_agent.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""A general dialog agent."""
|
||||
from typing import Optional, Union, Sequence
|
||||
|
||||
from ..message import Msg
|
||||
from .agent import AgentBase
|
||||
|
||||
|
||||
class DialogAgent(AgentBase):
|
||||
"""A simple agent used to perform a dialogue. Your can set its role by
|
||||
`sys_prompt`."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
sys_prompt: str,
|
||||
model_config_name: str,
|
||||
use_memory: bool = True,
|
||||
memory_config: Optional[dict] = None,
|
||||
) -> None:
|
||||
"""Initialize the dialog agent.
|
||||
|
||||
Arguments:
|
||||
name (`str`):
|
||||
The name of the agent.
|
||||
sys_prompt (`Optional[str]`):
|
||||
The system prompt of the agent, which can be passed by args
|
||||
or hard-coded in the agent.
|
||||
model_config_name (`str`):
|
||||
The name of the model config, which is used to load model from
|
||||
configuration.
|
||||
use_memory (`bool`, defaults to `True`):
|
||||
Whether the agent has memory.
|
||||
memory_config (`Optional[dict]`):
|
||||
The config of memory.
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
sys_prompt=sys_prompt,
|
||||
model_config_name=model_config_name,
|
||||
use_memory=use_memory,
|
||||
memory_config=memory_config,
|
||||
)
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
"""Reply function of the agent. Processes the input data,
|
||||
generates a prompt using the current dialogue memory and system
|
||||
prompt, and invokes the language model to produce a response. The
|
||||
response is then formatted and added to the dialogue memory.
|
||||
|
||||
Args:
|
||||
x (`Optional[Union[Msg, Sequence[Msg]]]`, defaults to `None`):
|
||||
The input message(s) to the agent, which also can be omitted if
|
||||
the agent doesn't need any input.
|
||||
|
||||
Returns:
|
||||
`Msg`: The output message generated by the agent.
|
||||
"""
|
||||
# record the input if needed
|
||||
if self.memory:
|
||||
self.memory.add(x)
|
||||
|
||||
# prepare prompt
|
||||
prompt = self.model.format(
|
||||
Msg("system", self.sys_prompt, role="system"),
|
||||
self.memory
|
||||
and self.memory.get_memory()
|
||||
or x, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
# call llm and generate response
|
||||
response = self.model(prompt).text
|
||||
msg = Msg(self.name, response, role="assistant")
|
||||
|
||||
# Print/speak the message in this agent's voice
|
||||
self.speak(msg)
|
||||
|
||||
# Record the message in memory
|
||||
if self.memory:
|
||||
self.memory.add(msg)
|
||||
|
||||
return msg
|
||||
123
AlgoriAgent/src/agentscope/agents/dict_dialog_agent.py
Normal file
123
AlgoriAgent/src/agentscope/agents/dict_dialog_agent.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""An agent that replies in a dictionary format."""
|
||||
from typing import Optional, Union, Sequence
|
||||
|
||||
from ..message import Msg
|
||||
from .agent import AgentBase
|
||||
from ..parsers import ParserBase
|
||||
|
||||
|
||||
class DictDialogAgent(AgentBase):
|
||||
"""An agent that generates response in a dict format, where user can
|
||||
specify the required fields in the response via specifying the parser
|
||||
|
||||
About parser, please refer to our
|
||||
[tutorial](https://modelscope.github.io/agentscope/en/tutorial/203-parser.html)
|
||||
|
||||
For usage example, please refer to the example of werewolf in
|
||||
`examples/game_werewolf`"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
sys_prompt: str,
|
||||
model_config_name: str,
|
||||
use_memory: bool = True,
|
||||
memory_config: Optional[dict] = None,
|
||||
max_retries: Optional[int] = 3,
|
||||
) -> None:
|
||||
"""Initialize the dict dialog agent.
|
||||
|
||||
Arguments:
|
||||
name (`str`):
|
||||
The name of the agent.
|
||||
sys_prompt (`Optional[str]`, defaults to `None`):
|
||||
The system prompt of the agent, which can be passed by args
|
||||
or hard-coded in the agent.
|
||||
model_config_name (`str`, defaults to None):
|
||||
The name of the model config, which is used to load model from
|
||||
configuration.
|
||||
use_memory (`bool`, defaults to `True`):
|
||||
Whether the agent has memory.
|
||||
memory_config (`Optional[dict]`, defaults to `None`):
|
||||
The config of memory.
|
||||
max_retries (`Optional[int]`, defaults to `None`):
|
||||
The maximum number of retries when failed to parse the model
|
||||
output.
|
||||
""" # noqa
|
||||
super().__init__(
|
||||
name=name,
|
||||
sys_prompt=sys_prompt,
|
||||
model_config_name=model_config_name,
|
||||
use_memory=use_memory,
|
||||
memory_config=memory_config,
|
||||
)
|
||||
|
||||
self.parser = None
|
||||
self.max_retries = max_retries
|
||||
|
||||
def set_parser(self, parser: ParserBase) -> None:
|
||||
"""Set response parser, which will provide 1) format instruction; 2)
|
||||
response parsing; 3) filtering fields when returning message, storing
|
||||
message in memory. So developers only need to change the
|
||||
parser, and the agent will work as expected.
|
||||
"""
|
||||
self.parser = parser
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
"""Reply function of the agent.
|
||||
Processes the input data, generates a prompt using the current
|
||||
dialogue memory and system prompt, and invokes the language
|
||||
model to produce a response. The response is then formatted
|
||||
and added to the dialogue memory.
|
||||
|
||||
Args:
|
||||
x (`Optional[Union[Msg, Sequence[Msg]]]`, defaults to `None`):
|
||||
The input message(s) to the agent, which also can be omitted if
|
||||
the agent doesn't need any input.
|
||||
|
||||
|
||||
Returns:
|
||||
`Msg`: The output message generated by the agent.
|
||||
|
||||
Raises:
|
||||
`json.decoder.JSONDecodeError`:
|
||||
If the response from the language model is not valid JSON,
|
||||
it defaults to treating the response as plain text.
|
||||
"""
|
||||
# record the input if needed
|
||||
if self.memory:
|
||||
self.memory.add(x)
|
||||
|
||||
# prepare prompt
|
||||
prompt = self.model.format(
|
||||
Msg("system", self.sys_prompt, role="system"),
|
||||
self.memory
|
||||
and self.memory.get_memory()
|
||||
or x, # type: ignore[arg-type]
|
||||
Msg("system", self.parser.format_instruction, "system"),
|
||||
)
|
||||
|
||||
# call llm
|
||||
res = self.model(
|
||||
prompt,
|
||||
parse_func=self.parser.parse,
|
||||
max_retries=self.max_retries,
|
||||
)
|
||||
|
||||
# Filter the parsed response by keys for storing in memory, returning
|
||||
# in the reply function, and feeding into the metadata field in the
|
||||
# returned message object.
|
||||
self.memory.add(
|
||||
Msg(self.name, self.parser.to_memory(res.parsed), "assistant"),
|
||||
)
|
||||
|
||||
msg = Msg(
|
||||
self.name,
|
||||
content=self.parser.to_content(res.parsed),
|
||||
role="assistant",
|
||||
metadata=self.parser.to_metadata(res.parsed),
|
||||
)
|
||||
self.speak(msg)
|
||||
|
||||
return msg
|
||||
18
AlgoriAgent/src/agentscope/agents/operator.py
Normal file
18
AlgoriAgent/src/agentscope/agents/operator.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""A common base class for AgentBase and PipelineBase"""
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Operator(ABC):
|
||||
"""
|
||||
Abstract base class `Operator` defines a protocol for classes that
|
||||
implement callable behavior.
|
||||
The class is designed to be subclassed with an overridden `__call__`
|
||||
method that specifies the execution logic for the operator.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> dict:
|
||||
"""Calling function"""
|
||||
194
AlgoriAgent/src/agentscope/agents/rag_agent.py
Normal file
194
AlgoriAgent/src/agentscope/agents/rag_agent.py
Normal file
@@ -0,0 +1,194 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This example shows how to build an agent with RAG
|
||||
with LlamaIndex.
|
||||
|
||||
Notice, this is a Beta version of RAG agent.
|
||||
"""
|
||||
|
||||
from typing import Any, Optional, Union, Sequence
|
||||
from loguru import logger
|
||||
|
||||
from agentscope.agents.agent import AgentBase
|
||||
from agentscope.message import Msg
|
||||
from agentscope.rag import Knowledge
|
||||
|
||||
CHECKING_PROMPT = """
|
||||
Is the retrieved content relevant to the query?
|
||||
Retrieved content: {}
|
||||
Query: {}
|
||||
Only answer YES or NO.
|
||||
"""
|
||||
|
||||
|
||||
class LlamaIndexAgent(AgentBase):
|
||||
"""
|
||||
A LlamaIndex agent build on LlamaIndex.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
sys_prompt: str,
|
||||
model_config_name: str,
|
||||
knowledge_list: list[Knowledge] = None,
|
||||
knowledge_id_list: list[str] = None,
|
||||
similarity_top_k: int = None,
|
||||
log_retrieval: bool = True,
|
||||
recent_n_mem_for_retrieve: int = 1,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the RAG LlamaIndexAgent
|
||||
Args:
|
||||
name (str):
|
||||
the name for the agent
|
||||
sys_prompt (str):
|
||||
system prompt for the RAG agent
|
||||
model_config_name (str):
|
||||
language model for the agent
|
||||
knowledge_list (list[Knowledge]):
|
||||
a list of knowledge.
|
||||
User can choose to pass a list knowledge object
|
||||
directly when initializing the RAG agent. Another
|
||||
choice can be passing a list of knowledge ids and
|
||||
obtain the knowledge with the `equip` function of a
|
||||
knowledge bank.
|
||||
knowledge_id_list (list[Knowledge]):
|
||||
a list of id of the knowledge.
|
||||
This is designed for easy setting up multiple RAG
|
||||
agents with a config file. To obtain the knowledge
|
||||
objects, users can pass this agent to the `equip`
|
||||
function in a knowledge bank to add corresponding
|
||||
knowledge to agent's self.knowledge_list.
|
||||
similarity_top_k (int):
|
||||
the number of most similar data blocks retrieved
|
||||
from each of the knowledge
|
||||
log_retrieval (bool):
|
||||
whether to print the retrieved content
|
||||
recent_n_mem_for_retrieve (int):
|
||||
the number of pieces of memory used as part of
|
||||
retrival query
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
sys_prompt=sys_prompt,
|
||||
model_config_name=model_config_name,
|
||||
)
|
||||
self.knowledge_list = knowledge_list or []
|
||||
self.knowledge_id_list = knowledge_id_list or []
|
||||
self.similarity_top_k = similarity_top_k
|
||||
self.log_retrieval = log_retrieval
|
||||
self.recent_n_mem_for_retrieve = recent_n_mem_for_retrieve
|
||||
self.description = kwargs.get("description", "")
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
"""
|
||||
Reply function of the RAG agent.
|
||||
Processes the input data,
|
||||
1) use the input data to retrieve with RAG function;
|
||||
2) generates a prompt using the current memory and system
|
||||
prompt;
|
||||
3) invokes the language model to produce a response. The
|
||||
response is then formatted and added to the dialogue memory.
|
||||
|
||||
Args:
|
||||
x (`Optional[Union[Msg, Sequence[Msg]]]`, defaults to `None`):
|
||||
The input message(s) to the agent, which also can be omitted if
|
||||
the agent doesn't need any input.
|
||||
|
||||
Returns:
|
||||
`Msg`: The output message generated by the agent.
|
||||
"""
|
||||
retrieved_docs_to_string = ""
|
||||
# record the input if needed
|
||||
if self.memory:
|
||||
self.memory.add(x)
|
||||
# in case no input is provided (e.g., in msghub),
|
||||
# use the memory as query
|
||||
history = self.memory.get_memory(
|
||||
recent_n=self.recent_n_mem_for_retrieve,
|
||||
)
|
||||
query = (
|
||||
"/n".join(
|
||||
[msg["content"] for msg in history],
|
||||
)
|
||||
if isinstance(history, list)
|
||||
else str(history)
|
||||
)
|
||||
elif x is not None:
|
||||
query = x.content
|
||||
else:
|
||||
query = ""
|
||||
|
||||
if len(query) > 0:
|
||||
# when content has information, do retrieval
|
||||
scores = []
|
||||
for knowledge in self.knowledge_list:
|
||||
retrieved_nodes = knowledge.retrieve(
|
||||
str(query),
|
||||
self.similarity_top_k,
|
||||
)
|
||||
for node in retrieved_nodes:
|
||||
scores.append(node.score)
|
||||
retrieved_docs_to_string += (
|
||||
"\n>>>> score:"
|
||||
+ str(node.score)
|
||||
+ "\n>>>> source:"
|
||||
+ str(node.node.get_metadata_str())
|
||||
+ "\n>>>> content:"
|
||||
+ node.get_content()
|
||||
)
|
||||
|
||||
if self.log_retrieval:
|
||||
self.speak("[retrieved]:" + retrieved_docs_to_string)
|
||||
|
||||
if max(scores) < 0.4:
|
||||
# if the max score is lower than 0.4, then we let LLM
|
||||
# decide whether the retrieved content is relevant
|
||||
# to the user input.
|
||||
msg = Msg(
|
||||
name="user",
|
||||
role="user",
|
||||
content=CHECKING_PROMPT.format(
|
||||
retrieved_docs_to_string,
|
||||
query,
|
||||
),
|
||||
)
|
||||
msg = self.model.format(msg)
|
||||
checking = self.model(msg)
|
||||
logger.info(checking)
|
||||
checking = checking.text.lower()
|
||||
if "no" in checking:
|
||||
retrieved_docs_to_string = "EMPTY"
|
||||
|
||||
# prepare prompt
|
||||
prompt = self.model.format(
|
||||
Msg(
|
||||
name="system",
|
||||
role="system",
|
||||
content=self.sys_prompt,
|
||||
),
|
||||
# {"role": "system", "content": retrieved_docs_to_string},
|
||||
self.memory.get_memory(
|
||||
recent_n=self.recent_n_mem_for_retrieve,
|
||||
),
|
||||
Msg(
|
||||
name="user",
|
||||
role="user",
|
||||
content="Context: " + retrieved_docs_to_string,
|
||||
),
|
||||
)
|
||||
|
||||
# call llm and generate response
|
||||
response = self.model(prompt).text
|
||||
msg = Msg(self.name, response)
|
||||
|
||||
# Print/speak the message in this agent's voice
|
||||
self.speak(msg)
|
||||
|
||||
if self.memory:
|
||||
# Record the message in memory
|
||||
self.memory.add(msg)
|
||||
|
||||
return msg
|
||||
316
AlgoriAgent/src/agentscope/agents/react_agent.py
Normal file
316
AlgoriAgent/src/agentscope/agents/react_agent.py
Normal file
@@ -0,0 +1,316 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""An agent class that implements the ReAct algorithm. The agent will reason
|
||||
and act iteratively to solve problems. More details can be found in the paper
|
||||
https://arxiv.org/abs/2210.03629.
|
||||
"""
|
||||
from typing import Any, Optional, Union, Sequence
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from agentscope.exception import ResponseParsingError, FunctionCallError
|
||||
from agentscope.agents import AgentBase
|
||||
from agentscope.message import Msg
|
||||
from agentscope.parsers import MarkdownJsonDictParser
|
||||
from agentscope.service import ServiceToolkit
|
||||
from agentscope.service.service_toolkit import ServiceFunction
|
||||
|
||||
INSTRUCTION_PROMPT = """## Your Role:
|
||||
You are a powerful AI agent and specifically aimed to use C# and Unity to create new Game Contents, which in the game project are named as 'Gurouce'.
|
||||
Gurouce is refer to anything that can be USED to make some influence in game, and your main job is follow user's instruction to create new game contents,
|
||||
This game is a 2D game in Unity with C# scripts.
|
||||
|
||||
## What You Should Do:
|
||||
0. Check user's instruction, make sure you know where the game project, and user's instruction is rational and can be realize. Otherwize, just finish.
|
||||
1. To realize a new Gurouce, you should first analyze the user's task, to answer three main questions:
|
||||
A. How the Gurouce find the target to influence?
|
||||
B. At what situation the Gurouce will start to influence?
|
||||
C. How the Gurouce influence the target?
|
||||
2. Then, open and read the source file, then inherit from them and override their functions to realize the three main questions:
|
||||
A. BaseFindTarget.cs
|
||||
B. BaseStartCondition.cs
|
||||
C. BaseInfluence.cs
|
||||
You should open and read the example Gurouce to learn how to use. The example Gurouce is RockGurouce, when using it, it will create a "Rock" Gurouce and fly from the game character towards where the mouse pointer is, and cause damage to the target it hits, then destory itself.
|
||||
3. Since you have to write some source files, make sure each file implement their job functionality, and all three questions above are anwsered well.
|
||||
3. Next, Call "compile" function, it will compile the source files and update game contents.
|
||||
4. If failed to compile, follow the error message and edit again, otherwise finish.
|
||||
|
||||
## Note:
|
||||
1. Fully understand the tool functions and their arguments before using them.
|
||||
2. Check the folder structure and relevant scripts before write any code.
|
||||
3. Your EVERY response MUST have at least one function call, and the function call must be in the "function" field.
|
||||
4. Make sure the types and values of the arguments you provided to the tool functions are correct.
|
||||
5. Don't take things for granted. For example, where you are, what's the time now, etc. You can try to use the tool functions to get information.
|
||||
6. If the function execution fails, you should analyze the error and try to solve it.
|
||||
7. You don't have to finish the whole task quickly, we will ask your several times, so follow the "What You Should Do" and where we have done, to respond once a step.
|
||||
|
||||
|
||||
## Resources:
|
||||
1. The tool functions you can use.
|
||||
2. A Specific Unity game project.
|
||||
|
||||
## Constraint:
|
||||
1. You should not use any other tool functions.
|
||||
2. There is a folder path 'Assets/HotUpdate/Gurouce/', you must create a subfolder in it, and you can only edit within the subfolder. But you can read any other file if need. BTW, the base scripts are in 'BaseScripts' subfolder and RockGurouce is in 'RockGurouce' subfolder.
|
||||
3. You have to decide a Gurouce name, and you have to change "Base" to the Gurouce name you decided when you write the source files' names.
|
||||
""" # noqa
|
||||
|
||||
|
||||
class ReActAgent(AgentBase):
|
||||
"""An agent class that implements the ReAct algorithm. More details refer
|
||||
to https://arxiv.org/abs/2210.03629.
|
||||
|
||||
Note this is an example implementation of ReAct algorithm in AgentScope.
|
||||
We follow the idea within the paper, but the detailed prompt engineering
|
||||
maybe different. Developers are encouraged to modify the prompt to fit
|
||||
their own needs.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
model_config_name: str,
|
||||
service_toolkit: ServiceToolkit = None,
|
||||
sys_prompt: str = "You're a helpful assistant. Your name is {name}.",
|
||||
max_iters: int = 10,
|
||||
verbose: bool = True,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize the ReAct agent with the given name, model config name
|
||||
and tools.
|
||||
|
||||
Args:
|
||||
name (`str`):
|
||||
The name of the agent.
|
||||
sys_prompt (`str`):
|
||||
The system prompt of the agent.
|
||||
model_config_name (`str`):
|
||||
The name of the model config, which is used to load model from
|
||||
configuration.
|
||||
service_toolkit (`ServiceToolkit`):
|
||||
A `ServiceToolkit` object that contains the tool functions.
|
||||
max_iters (`int`, defaults to `10`):
|
||||
The maximum number of iterations of the reasoning-acting loops.
|
||||
verbose (`bool`, defaults to `True`):
|
||||
Whether to print the detailed information during reasoning and
|
||||
acting steps. If `False`, only the content in speak field will
|
||||
be print out.
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
sys_prompt=sys_prompt,
|
||||
model_config_name=model_config_name,
|
||||
)
|
||||
|
||||
# TODO: To compatible with the old version, which will be deprecated
|
||||
# soon
|
||||
if "tools" in kwargs:
|
||||
logger.warning(
|
||||
"The argument `tools` will be deprecated soon. "
|
||||
"Please use `service_toolkit` instead. Example refers to "
|
||||
"https://github.com/modelscope/agentscope/blob/main/"
|
||||
"examples/conversation_with_react_agent/code/"
|
||||
"conversation_with_react_agent.py",
|
||||
)
|
||||
|
||||
service_funcs = {}
|
||||
for func, json_schema in kwargs["tools"]:
|
||||
name = json_schema["function"]["name"]
|
||||
service_funcs[name] = ServiceFunction(
|
||||
name=name,
|
||||
original_func=func,
|
||||
processed_func=func,
|
||||
json_schema=json_schema,
|
||||
)
|
||||
|
||||
if service_toolkit is None:
|
||||
service_toolkit = ServiceToolkit()
|
||||
service_toolkit.service_funcs = service_funcs
|
||||
else:
|
||||
service_toolkit.service_funcs.update(service_funcs)
|
||||
|
||||
elif service_toolkit is None:
|
||||
raise ValueError(
|
||||
"The argument `service_toolkit` is required to initialize "
|
||||
"the ReActAgent.",
|
||||
)
|
||||
|
||||
self.service_toolkit = service_toolkit
|
||||
self.verbose = verbose
|
||||
self.max_iters = max_iters
|
||||
|
||||
if not sys_prompt.endswith("\n"):
|
||||
sys_prompt = sys_prompt + "\n"
|
||||
|
||||
self.sys_prompt = "\n".join(
|
||||
[
|
||||
# The brief intro of the role and target
|
||||
sys_prompt.format(name=self.name),
|
||||
# The instruction prompt for tools
|
||||
self.service_toolkit.tools_instruction,
|
||||
# The detailed instruction prompt for the agent
|
||||
INSTRUCTION_PROMPT,
|
||||
],
|
||||
)
|
||||
|
||||
# Put sys prompt into memory
|
||||
self.memory.add(Msg("system", self.sys_prompt, role="system"))
|
||||
|
||||
# Initialize a parser object to formulate the response from the model
|
||||
self.parser = MarkdownJsonDictParser(
|
||||
content_hint={
|
||||
"thought": "what you thought",
|
||||
"speak": "what you speak",
|
||||
"function": service_toolkit.tools_calling_format,
|
||||
},
|
||||
required_keys=["thought", "speak", "function"],
|
||||
# Only print the speak field when verbose is False
|
||||
keys_to_content=True if self.verbose else "speak",
|
||||
)
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
"""The reply function that achieves the ReAct algorithm.
|
||||
The more details please refer to https://arxiv.org/abs/2210.03629"""
|
||||
|
||||
self.memory.add(x)
|
||||
|
||||
for _ in range(self.max_iters):
|
||||
# Step 1: Thought
|
||||
if self.verbose:
|
||||
self.speak(f" ITER {_+1}, STEP 1: REASONING ".center(70, "#"))
|
||||
|
||||
# Prepare hint to remind model what the response format is
|
||||
# Won't be recorded in memory to save tokens
|
||||
hint_msg = Msg(
|
||||
"system",
|
||||
self.parser.format_instruction,
|
||||
role="system",
|
||||
echo=self.verbose,
|
||||
)
|
||||
|
||||
# Prepare prompt for the model
|
||||
prompt = self.model.format(self.memory.get_memory(), hint_msg)
|
||||
print('====================================Prompt==========================================')
|
||||
print(prompt)
|
||||
# Generate and parse the response
|
||||
try:
|
||||
res = self.model(
|
||||
prompt,
|
||||
parse_func=self.parser.parse,
|
||||
max_retries=1,
|
||||
)
|
||||
|
||||
print('====================================Result Return==========================================')
|
||||
print(res)
|
||||
print('====================================Result Parsed==========================================')
|
||||
print(res.parsed)
|
||||
# Record the response in memory
|
||||
self.memory.add(
|
||||
Msg(
|
||||
self.name,
|
||||
self.parser.to_memory(res.parsed),
|
||||
"assistant",
|
||||
),
|
||||
)
|
||||
|
||||
# Print out the response
|
||||
msg_returned = Msg(
|
||||
self.name,
|
||||
self.parser.to_content(res.parsed),
|
||||
"assistant",
|
||||
)
|
||||
print('====================================Speak==========================================')
|
||||
print(msg_returned)
|
||||
self.speak(msg_returned)
|
||||
|
||||
# Skip the next steps if no need to call tools
|
||||
# The parsed field is a dictionary
|
||||
print('====================================Function==========================================')
|
||||
print(res.parsed["function"])
|
||||
arg_function = res.parsed["function"]
|
||||
if (
|
||||
isinstance(arg_function, str)
|
||||
and arg_function in ["[]", ""]
|
||||
or isinstance(arg_function, list)
|
||||
and len(arg_function) == 0
|
||||
):
|
||||
# Only the speak field is exposed to users or other agents
|
||||
return msg_returned
|
||||
|
||||
# Only catch the response parsing error and expose runtime
|
||||
# errors to developers for debugging
|
||||
except ResponseParsingError as e:
|
||||
# Print out raw response from models for developers to debug
|
||||
response_msg = Msg(self.name, e.raw_response, "assistant")
|
||||
self.speak(response_msg)
|
||||
|
||||
# Re-correct by model itself
|
||||
error_msg = Msg("system", str(e), "system")
|
||||
self.speak(error_msg)
|
||||
|
||||
self.memory.add([response_msg, error_msg])
|
||||
|
||||
# Skip acting step to re-correct the response
|
||||
continue
|
||||
|
||||
# Step 2: Acting
|
||||
if self.verbose:
|
||||
self.speak(f" ITER {_+1}, STEP 2: ACTING ".center(70, "#"))
|
||||
|
||||
# Parse, check and execute the tool functions in service toolkit
|
||||
try:
|
||||
|
||||
if res.parsed["function"] == "finish":
|
||||
hint_msg = Msg(
|
||||
"system",
|
||||
"You have Finished the task."
|
||||
"Now generate a reply by summarizing the current "
|
||||
"situation.",
|
||||
role="system",
|
||||
echo=self.verbose,
|
||||
)
|
||||
|
||||
# Generate a reply by summarizing the current situation
|
||||
prompt = self.model.format(self.memory.get_memory(), hint_msg)
|
||||
res = self.model(prompt)
|
||||
res_msg = Msg(self.name, res.text, "assistant")
|
||||
self.speak(res_msg)
|
||||
return res_msg
|
||||
|
||||
execute_results = self.service_toolkit.parse_and_call_func(
|
||||
res.parsed["function"],
|
||||
)
|
||||
|
||||
# Note: Observing the execution results and generate response
|
||||
# are finished in the next reasoning step. We just put the
|
||||
# execution results into memory, and wait for the next loop
|
||||
# to generate response.
|
||||
|
||||
# Record execution results into memory as system message
|
||||
msg_res = Msg("system", execute_results, "system")
|
||||
self.speak(msg_res)
|
||||
self.memory.add(msg_res)
|
||||
|
||||
except FunctionCallError as e:
|
||||
# Catch the function calling error that can be handled by
|
||||
# the model
|
||||
error_msg = Msg("system", str(e), "system")
|
||||
self.speak(error_msg)
|
||||
self.memory.add(error_msg)
|
||||
|
||||
# Exceed the maximum iterations
|
||||
hint_msg = Msg(
|
||||
"system",
|
||||
"You have failed to generate a response in the maximum "
|
||||
"iterations. Now generate a reply by summarizing the current "
|
||||
"situation.",
|
||||
role="system",
|
||||
echo=self.verbose,
|
||||
)
|
||||
|
||||
# Generate a reply by summarizing the current situation
|
||||
prompt = self.model.format(self.memory.get_memory(), hint_msg)
|
||||
res = self.model(prompt)
|
||||
res_msg = Msg(self.name, res.text, "assistant")
|
||||
self.speak(res_msg)
|
||||
|
||||
return res_msg
|
||||
178
AlgoriAgent/src/agentscope/agents/rpc_agent.py
Normal file
178
AlgoriAgent/src/agentscope/agents/rpc_agent.py
Normal file
@@ -0,0 +1,178 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Base class for Rpc Agent """
|
||||
from typing import Type, Optional, Union, Sequence
|
||||
|
||||
from agentscope.agents.agent import AgentBase
|
||||
from agentscope.message import (
|
||||
PlaceholderMessage,
|
||||
serialize,
|
||||
Msg,
|
||||
)
|
||||
from agentscope.rpc import RpcAgentClient
|
||||
from agentscope.server.launcher import RpcAgentServerLauncher
|
||||
from agentscope.studio._client import _studio_client
|
||||
|
||||
|
||||
class RpcAgent(AgentBase):
|
||||
"""A wrapper to extend an AgentBase into a gRPC Client."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
host: str = "localhost",
|
||||
port: int = None,
|
||||
agent_class: Type[AgentBase] = None,
|
||||
agent_configs: Optional[dict] = None,
|
||||
max_pool_size: int = 8192,
|
||||
max_timeout_seconds: int = 1800,
|
||||
local_mode: bool = True,
|
||||
lazy_launch: bool = True,
|
||||
agent_id: str = None,
|
||||
connect_existing: bool = False,
|
||||
) -> None:
|
||||
"""Initialize a RpcAgent instance.
|
||||
|
||||
Args:
|
||||
name (`str`): the name of the agent.
|
||||
host (`str`, defaults to `localhost`):
|
||||
Hostname of the rpc agent server.
|
||||
port (`int`, defaults to `None`):
|
||||
Port of the rpc agent server.
|
||||
agent_class (`Type[AgentBase]`):
|
||||
the AgentBase subclass of the source agent.
|
||||
agent_configs (`dict`): The args used to
|
||||
initialize the agent, generated by `_AgentMeta`.
|
||||
max_pool_size (`int`, defaults to `8192`):
|
||||
Max number of task results that the server can accommodate.
|
||||
max_timeout_seconds (`int`, defaults to `1800`):
|
||||
Timeout for task results.
|
||||
local_mode (`bool`, defaults to `True`):
|
||||
Whether the started gRPC server only listens to local
|
||||
requests.
|
||||
lazy_launch (`bool`, defaults to `True`):
|
||||
Only launch the server when the agent is called.
|
||||
agent_id (`str`, defaults to `None`):
|
||||
The agent id of this instance. If `None`, it will
|
||||
be generated randomly.
|
||||
connect_existing (`bool`, defaults to `False`):
|
||||
Set to `True`, if the agent is already running on the agent
|
||||
server.
|
||||
"""
|
||||
super().__init__(name=name)
|
||||
self.agent_class = agent_class
|
||||
self.agent_configs = agent_configs
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.server_launcher = None
|
||||
self.client = None
|
||||
self.connect_existing = connect_existing
|
||||
if agent_id is not None:
|
||||
self._agent_id = agent_id
|
||||
# if host and port are not provided, launch server locally
|
||||
launch_server = port is None
|
||||
if launch_server:
|
||||
self.host = "localhost"
|
||||
studio_url = None
|
||||
if _studio_client.active:
|
||||
studio_url = _studio_client.studio_url
|
||||
self.server_launcher = RpcAgentServerLauncher(
|
||||
host=self.host,
|
||||
port=port,
|
||||
max_pool_size=max_pool_size,
|
||||
max_timeout_seconds=max_timeout_seconds,
|
||||
local_mode=local_mode,
|
||||
custom_agents=[agent_class],
|
||||
studio_url=studio_url,
|
||||
)
|
||||
if not lazy_launch:
|
||||
self._launch_server()
|
||||
else:
|
||||
self.client = RpcAgentClient(
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
agent_id=self.agent_id,
|
||||
)
|
||||
if not self.connect_existing:
|
||||
self.client.create_agent(agent_configs)
|
||||
|
||||
def _launch_server(self) -> None:
|
||||
"""Launch a rpc server and update the port and the client"""
|
||||
self.server_launcher.launch()
|
||||
self.port = self.server_launcher.port
|
||||
self.client = RpcAgentClient(
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
agent_id=self.agent_id,
|
||||
)
|
||||
self.client.create_agent(self.agent_configs)
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
if self.client is None:
|
||||
self._launch_server()
|
||||
return PlaceholderMessage(
|
||||
name=self.name,
|
||||
content=None,
|
||||
client=self.client,
|
||||
x=x,
|
||||
)
|
||||
|
||||
def observe(self, x: Union[dict, Sequence[dict]]) -> None:
|
||||
if self.client is None:
|
||||
self._launch_server()
|
||||
self.client.call_func(
|
||||
func_name="_observe",
|
||||
value=serialize(x), # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
def clone_instances(
|
||||
self,
|
||||
num_instances: int,
|
||||
including_self: bool = True,
|
||||
) -> Sequence[AgentBase]:
|
||||
"""
|
||||
Clone a series of this instance with different agent_id and
|
||||
return them as a list.
|
||||
|
||||
Args:
|
||||
num_instances (`int`): The number of instances in the returned
|
||||
list.
|
||||
including_self (`bool`): Whether to include the instance calling
|
||||
this method in the returned list.
|
||||
|
||||
Returns:
|
||||
`Sequence[AgentBase]`: A list of agent instances.
|
||||
"""
|
||||
generated_instance_number = (
|
||||
num_instances - 1 if including_self else num_instances
|
||||
)
|
||||
generated_instances = []
|
||||
|
||||
# launch the server before clone instances
|
||||
if self.client is None:
|
||||
self._launch_server()
|
||||
|
||||
# put itself as the first element of the returned list
|
||||
if including_self:
|
||||
generated_instances.append(self)
|
||||
|
||||
# clone instances without agent server
|
||||
for _ in range(generated_instance_number):
|
||||
new_agent_id = self.client.call_func("_clone_agent")
|
||||
generated_instances.append(
|
||||
RpcAgent(
|
||||
name=self.name,
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
agent_id=new_agent_id,
|
||||
connect_existing=True,
|
||||
),
|
||||
)
|
||||
return generated_instances
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop the RpcAgent and the rpc server."""
|
||||
if self.server_launcher is not None:
|
||||
self.server_launcher.shutdown()
|
||||
|
||||
def __del__(self) -> None:
|
||||
self.stop()
|
||||
79
AlgoriAgent/src/agentscope/agents/text_to_image_agent.py
Normal file
79
AlgoriAgent/src/agentscope/agents/text_to_image_agent.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""An agent that convert text to image."""
|
||||
|
||||
from typing import Optional, Union, Sequence
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from .agent import AgentBase
|
||||
from ..message import Msg
|
||||
|
||||
|
||||
class TextToImageAgent(AgentBase):
|
||||
"""
|
||||
A agent used to perform text to image tasks.
|
||||
|
||||
TODO: change the agent into a service.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
model_config_name: str,
|
||||
use_memory: bool = True,
|
||||
memory_config: Optional[dict] = None,
|
||||
) -> None:
|
||||
"""Initialize the text to image agent.
|
||||
|
||||
Arguments:
|
||||
name (`str`):
|
||||
The name of the agent.
|
||||
model_config_name (`str`, defaults to None):
|
||||
The name of the model config, which is used to load model from
|
||||
configuration.
|
||||
use_memory (`bool`, defaults to `True`):
|
||||
Whether the agent has memory.
|
||||
memory_config (`Optional[dict]`):
|
||||
The config of memory.
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
sys_prompt="",
|
||||
model_config_name=model_config_name,
|
||||
use_memory=use_memory,
|
||||
memory_config=memory_config,
|
||||
)
|
||||
|
||||
logger.warning(
|
||||
"The `TextToImageAgent` will be deprecated in v0.0.6, "
|
||||
"please use `text_to_image` service and `ReActAgent` instead.",
|
||||
)
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
if self.memory:
|
||||
self.memory.add(x)
|
||||
if x is None:
|
||||
# get the last message from memory
|
||||
if self.memory and self.memory.size() > 0:
|
||||
x = self.memory.get_memory()[-1]
|
||||
else:
|
||||
return Msg(
|
||||
self.name,
|
||||
content="Please provide a text prompt to generate image.",
|
||||
role="assistant",
|
||||
)
|
||||
image_urls = self.model(x.content).image_urls
|
||||
# TODO: optimize the construction of content
|
||||
msg = Msg(
|
||||
self.name,
|
||||
content="This is the generated image",
|
||||
role="assistant",
|
||||
url=image_urls,
|
||||
)
|
||||
|
||||
self.speak(msg)
|
||||
|
||||
if self.memory:
|
||||
self.memory.add(msg)
|
||||
|
||||
return msg
|
||||
150
AlgoriAgent/src/agentscope/agents/user_agent.py
Normal file
150
AlgoriAgent/src/agentscope/agents/user_agent.py
Normal file
@@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""User Agent class"""
|
||||
import time
|
||||
from typing import Union, Sequence
|
||||
from typing import Optional
|
||||
from loguru import logger
|
||||
|
||||
from agentscope.agents import AgentBase
|
||||
from agentscope.studio._client import _studio_client
|
||||
from agentscope.message import Msg
|
||||
from agentscope.web.gradio.utils import user_input
|
||||
|
||||
|
||||
class UserAgent(AgentBase):
|
||||
"""User agent class"""
|
||||
|
||||
def __init__(self, name: str = "User", require_url: bool = False) -> None:
|
||||
"""Initialize a UserAgent object.
|
||||
|
||||
Arguments:
|
||||
name (`str`, defaults to `"User"`):
|
||||
The name of the agent. Defaults to "User".
|
||||
require_url (`bool`, defaults to `False`):
|
||||
Whether the agent requires user to input a URL. Defaults to
|
||||
False. The URL can lead to a website, a file,
|
||||
or a directory. It will be added into the generated message
|
||||
in field `url`.
|
||||
"""
|
||||
super().__init__(name=name)
|
||||
|
||||
self.name = name
|
||||
self.require_url = require_url
|
||||
|
||||
def reply(
|
||||
self,
|
||||
x: Optional[Union[Msg, Sequence[Msg]]] = None,
|
||||
required_keys: Optional[Union[list[str], str]] = None,
|
||||
timeout: Optional[int] = None,
|
||||
) -> Msg:
|
||||
"""
|
||||
Processes the input provided by the user and stores it in memory,
|
||||
potentially formatting it with additional provided details.
|
||||
|
||||
The method prompts the user for input, then optionally prompts for
|
||||
additional specifics based on the provided format keys. All
|
||||
information is encapsulated in a message object, which is then
|
||||
added to the object's memory.
|
||||
|
||||
Arguments:
|
||||
x (`Optional[Union[Msg, Sequence[Msg]]]`, defaults to `None`):
|
||||
The input message(s) to the agent, which also can be omitted if
|
||||
the agent doesn't need any input.
|
||||
required_keys \
|
||||
(`Optional[Union[list[str], str]]`, defaults to `None`):
|
||||
Strings that requires user to input, which will be used as
|
||||
the key of the returned dict. Defaults to None.
|
||||
timeout (`Optional[int]`, defaults to `None`):
|
||||
Raise `TimeoutError` if user exceed input time, set to None
|
||||
for no limit.
|
||||
|
||||
Returns:
|
||||
`Msg`: The output message generated by the agent.
|
||||
"""
|
||||
if self.memory:
|
||||
self.memory.add(x)
|
||||
|
||||
if _studio_client.active:
|
||||
logger.info(
|
||||
f"Waiting for input from:\n\n"
|
||||
f" * {_studio_client.get_run_detail_page_url()}\n",
|
||||
)
|
||||
raw_input = _studio_client.get_user_input(
|
||||
agent_id=self.agent_id,
|
||||
name=self.name,
|
||||
require_url=self.require_url,
|
||||
required_keys=required_keys,
|
||||
)
|
||||
|
||||
print("Python: receive ", raw_input)
|
||||
content = raw_input["content"]
|
||||
url = raw_input["url"]
|
||||
kwargs = {}
|
||||
else:
|
||||
# TODO: To avoid order confusion, because `input` print much
|
||||
# quicker than logger.chat
|
||||
time.sleep(0.5)
|
||||
content = user_input(timeout=timeout)
|
||||
kwargs = {}
|
||||
if required_keys is not None:
|
||||
if isinstance(required_keys, str):
|
||||
required_keys = [required_keys]
|
||||
|
||||
for key in required_keys:
|
||||
kwargs[key] = input(f"{key}: ")
|
||||
|
||||
# Input url of file, image, video, audio or website
|
||||
url = None
|
||||
if self.require_url:
|
||||
url = input("URL (or Enter to skip): ")
|
||||
if url == "":
|
||||
url = None
|
||||
|
||||
# Add additional keys
|
||||
msg = Msg(
|
||||
name=self.name,
|
||||
role="user",
|
||||
content=content,
|
||||
url=url,
|
||||
**kwargs, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
self.speak(msg)
|
||||
|
||||
# Add to memory
|
||||
if self.memory:
|
||||
self.memory.add(msg)
|
||||
|
||||
return msg
|
||||
|
||||
def speak(
|
||||
self,
|
||||
content: Union[str, Msg],
|
||||
) -> None:
|
||||
"""
|
||||
Speak out the message generated by the agent. If a string is given,
|
||||
a Msg object will be created with the string as the content.
|
||||
|
||||
Args:
|
||||
content (`Union[str, Msg]`):
|
||||
The content of the message to be spoken out. If a string is
|
||||
given, a Msg object will be created with the agent's name, role
|
||||
as "user", and the given string as the content.
|
||||
|
||||
"""
|
||||
if isinstance(content, str):
|
||||
msg = Msg(
|
||||
name=self.name,
|
||||
content=content,
|
||||
role="assistant",
|
||||
)
|
||||
_studio_client.push_message(msg)
|
||||
elif isinstance(content, Msg):
|
||||
msg = content
|
||||
else:
|
||||
raise TypeError(
|
||||
"From version 0.0.5, the speak method only accepts str or Msg "
|
||||
f"object, got {type(content)} instead.",
|
||||
)
|
||||
|
||||
logger.chat(msg, disable_gradio=True)
|
||||
Reference in New Issue
Block a user