- 大家好,我是 同学小张,日常分享AI知识和实战案例
- 欢迎 点赞 + 关注 👏,持续学习,持续干货输出。
- +v: jasper_8017 一起交流💬,一起进步💪。
- 微信公众号也可搜【同学小张】 🙏
本站文章一览:
书接上篇文章,上篇文章我们运行了第一个AgentScope程序,里面我们创建了两个智能体:DialogAgent
对话智能体 和 UserAgent
用户智能体。本文我们来深入源码,看AgentScope框架内,这些智能体是如何实现的。
文章目录
0. 智能体基类 - AgentBase
class AgentBase(Operator, metaclass=_RecordInitSettingMeta):
"""Base class for all agents.
All agents should inherit from this class and implement the `reply`
function.
"""
这是AgentScope中智能体的基类,所有智能体的定义都需要继承它,并且重写其 reply
函数。
0.1 初始化过程
0.1.1 总过程
初始化实现的功能:
(1)初始化的过程,name
是必须参数。其余都可选。
(2)load_model_by_config_name
来加载模型配置,后面细说。
(3)use_memory
默认为True
,使用memory
时,memory
是TemporaryMemory
类型,并接收一个可选的 memory_config
参数。
def __init__(
self,
name: str,
sys_prompt: Optional[str] = None,
model_config_name: str = None,
use_memory: bool = True,
memory_config: Optional[dict] = None,
) -> 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.
"""
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
0.1.2 加载模型配置 - load_model_by_config_name
def _get_model_wrapper(model_type: str) -> Type[ModelWrapperBase]:
......
if model_type in ModelWrapperBase.type_registry:
return ModelWrapperBase.type_registry[ # type: ignore[return-value]
model_type
]
elif model_type in ModelWrapperBase.registry:
return ModelWrapperBase.registry[ # type: ignore[return-value]
model_type
]
elif model_type in ModelWrapperBase.deprecated_type_registry:
cls = ModelWrapperBase.deprecated_type_registry[model_type]
......
return cls # type: ignore[return-value]
else:
......
return PostAPIModelWrapperBase
def load_model_by_config_name(config_name: str) -> ModelWrapperBase:
"""Load the model by config name."""
......
return _get_model_wrapper(model_type=model_type)(**kwargs)
从源码看,这个函数的作用是根据你配置中的模型名称来加载模型的APIWrapper,例如我们上篇文章中的"model_type": "openai"
。理论上,这里的"model_type
"的值,只要是下面图中的任意一个即可:
0.1.3 模型配置的一些细节
在上篇文章中的配置部分我有一些疑问:
今天通过源码知道了其中的门道,看下面关于OpenAI接口的封装:
model_name
是在外面赋值的,所以必须要有。
api_key
是通过OpenAI接口传递进去的参数,根据我们先前的使用经验,这里可以不传,只要环境变量中存在 OPENAI_API_KEY
和 OPENAI_BASE_URL
即可使用。
1. 对话智能体 - DialogAgent
定义如下:通过 sys_prompt
参数指定其承担的角色,例如 “你是一个Python专家” 让其充当Python专家给你解决问题。
class DialogAgent(AgentBase):
"""A simple agent used to perform a dialogue. Your can set its role by
`sys_prompt`."""
1.1 初始化过程
def __init__(
self,
name: str,
sys_prompt: str,
model_config_name: str,
use_memory: bool = True,
memory_config: Optional[dict] = None,
prompt_type: Optional[PromptType] = 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.
prompt_type (`Optional[PromptType]`, defaults to
`PromptType.LIST`):
The type of the prompt organization, chosen from
`PromptType.LIST` or `PromptType.STRING`.
"""
初始化参数中 name
、sys_prompt
、model_config_name
是必须设置的。
1.2 重写 reply
函数
这个reply
函数是必须重写的。下面来看源码:
def reply(self, x: dict = None) -> dict:
"""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 (`dict`, defaults to `None`):
A dictionary representing the user's input to the agent. This
input is added to the dialogue memory if provided. Defaults to
None.
Returns:
A dictionary representing the message generated by the agent in
response to the user's input.
"""
# 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(), # 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
(1)首先是 memory 的操作,开始时将输入信息添加到 memory 中,结束前将大模型的回复写入到 memory 中:
if self.memory:
self.memory.add(x)
......
if self.memory:
self.memory.add(msg)
(2)prompt组装部分:将 system_prompt
和 memory
的内容组装成OpenAI需要的message列表。
def format(
self,
*args: Union[Msg, Sequence[Msg]],
) -> List[dict]:
......
messages = []
for arg in args:
if arg is None:
continue
if isinstance(arg, Msg):
messages.append(
{
"role": arg.role,
"name": arg.name,
"content": _convert_to_str(arg.content),
},
)
elif isinstance(arg, list):
messages.extend(self.format(*arg))
......
return messages
prompt = self.model.format(
Msg("system", self.sys_prompt, role="system"),
self.memory and self.memory.get_memory(), # type: ignore[arg-type]
)
(3)调用大模型获取结果
response = self.model(prompt).text
1.3 小结
所以,对话智能体的实现就是将 system_prompt
和 对话的 memory
组装成prompt,然后让大模型回复。
2. 用户智能体 - UserAgent
2.1 初始化过程
所有参数都是可选的,默认名称是 “User” :
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
2.2 重写 reply
函数
实现的功能就是接收用户输入:content = user_input(timeout=timeout)
def user_input(
prefix: str = "User input: ",
timeout: Optional[int] = None,
) -> str:
"""get user input"""
if hasattr(thread_local_data, "uid"):
content = get_player_input(
timeout=timeout,
uid=thread_local_data.uid,
)
else:
if timeout:
from inputimeout import inputimeout, TimeoutOccurred
try:
content = inputimeout(prefix, timeout=timeout)
except TimeoutOccurred as exc:
raise TimeoutError("timed out") from exc
else:
content = input(prefix)
return content
def reply(
self,
x: dict = None,
required_keys: Optional[Union[list[str], str]] = None,
timeout: Optional[int] = None,
) -> dict:
......
if self.memory:
self.memory.add(x)
......
content = user_input(timeout=timeout)
......
# Add to memory
if self.memory:
self.memory.add(msg)
return msg
2.3 小结
所以,UserAgent的作用就是接收用户输入,让人参与到多智能体的交互中。
3. 总结
本文主要看了下AgentScope中智能体agent的定义源码,深入学习了agent初始化的过程,配置的加载等流程。挑选了两个简单的agent - DialogAgent 和 UserAgent进行了详细学习。个性化Agent的定义,需要继承AgentBase基类,重写其reply函数,在reply函数中,定义个性化Agent的个性化动作。
AgentScope目前还有一些其它的个性化Agent,例如react_agent、rpc_agent等,咱们后面用到再学。
如果觉得本文对你有帮助,麻烦点个赞和关注呗 ~~~
- 大家好,我是 同学小张,日常分享AI知识和实战案例
- 欢迎 点赞 + 关注 👏,持续学习,持续干货输出。
- +v: jasper_8017 一起交流💬,一起进步💪。
- 微信公众号也可搜【同学小张】 🙏
本站文章一览: