注意
Go to the end 下载完整示例代码。
智能体钩子¶
Hooks是AgentScope中的扩展点,允许开发者在特定执行点自定义智能体行为,提供了一种无需修改智能体核心实现即可灵活修改或扩展其功能的方式。
在AgentScope中,钩子函数围绕智能体的核心功能实现:
智能体类别 |
核心功能 |
挂钩类型 |
描述 |
|---|---|---|---|
AgentBase &其子类
|
|
pre_replypost_reply |
在智能体回复消息前后触发的钩子 |
|
pre_printpost_print |
向目标输出(如终端、网页界面)打印消息前后的钩子函数 |
|
|
pre_observepost_observe |
在观察来自环境或其他智能体的消息前后执行的钩子 |
|
ReActAgentBase &其子类别
|
replyprintobserve |
pre_replypost_replypre_printpost_printpre_observepost_observe |
|
|
pre_reasoningpost_reasoning |
智能体推理过程之前/之后的钩子函数 |
|
|
pre_actingpost_acting |
Agent 动作过程前后的钩子函数 |
提示
由于Agent中的钩子是通过元类实现的,因此它们支持继承。
为简化使用,AgentScope为所有钩子提供了统一的签名。
import asyncio
from typing import Any, Type
from agentscope.agent import ReActAgentBase, AgentBase
from agentscope.message import Msg
钩子签名¶
AgentScope 提供以下统一的前后钩子签名:
预执行钩子签名
名称 |
描述 |
|
|---|---|---|
Arguments |
|
智能体实例 |
|
目标函数的输入参数
函数,或修改后的参数
根据最近的非空返回值
先前钩子的值
|
|
Returns |
|
修改的参数或None |
注意
核心函数的所有位置参数和关键字参数都以单个 kwargs 字典形式传递给钩子函数
前置钩子模板定义如下:
def pre_hook_template(
self: AgentBase | ReActAgentBase,
kwargs: dict[str, Any],
) -> dict[str, Any] | None: # The modified displayed message
"""Pre hook template."""
pass
后钩子签名
对于后钩子(post hooks),签名中会额外添加一个output参数,它代表目标函数的输出。
如果核心函数没有输出,output参数将为None。
名称 |
描述 |
|
|---|---|---|
参数 |
|
智能体实例 |
|
包含所有参数的字典
目标函数的
|
|
|
目标函数的输出或
最近的非空返回值
来自之前的钩子函数
|
|
返回 |
|
修改后的参数或无返回值 |
def post_hook_template(
self: AgentBase | ReActAgentBase,
kwargs: dict[str, Any],
output: Any, # The output of the target function
) -> Any: # The modified output
"""Post hook template."""
pass
钩子管理¶
AgentScope提供实例级和类级的钩子,具体取决于钩子的有效作用域。 它们的执行顺序如下:
AgentScope 提供了内置方法来在实例和类级别管理钩子,如下所示:
级别 |
方法 |
描述 |
|---|---|---|
实例层面 |
|
为当前对象注册一个钩子,使用
给定钩子类型和名称。
|
|
移除当前对象的钩子
给定钩子类型与名称.
|
|
|
使用以下内容清除当前对象的所有钩子:
给定挂钩类型。
|
|
类级别 |
|
为该类的所有对象注册钩子
使用给定的钩子类型和名称。
|
|
移除该类别所有对象的钩子
使用给定的钩子类型和名称。
|
|
|
清除所有对象中的所有钩子
带给定钩子类型的类。
|
使用钩子时,您必须遵循以下规则:
重要
执行顺序
Hooks 按照注册顺序执行
多个钩子可以被串联在一起
返回值处理
对于前置钩子:非None的返回值将被传递给下一个钩子或核心函数
当一个钩子返回 None 时,下一个钩子将使用先前钩子中最近的非 None 返回值。
如果所有之前的钩子都返回 None,下一个钩子将接收一份原始参数的副本
最终的非None返回值(如果所有钩子都返回None,则为原始参数)会被传递给核心函数
对于后置钩子:工作方式与前置钩子相同。
重要:切勿在钩子中调用核心函数(reply/speak/observe/_reasoning/_acting)以避免无限循环
# Create a simple test agent class
class TestAgent(AgentBase):
"""A test agent for demonstrating hooks."""
async def reply(self, msg: Msg) -> Msg:
"""Reply to the message."""
return msg
我们创建一个实例级钩子和一个类级钩子,用于在回复前修改消息内容。
# Create two pre-reply hooks
def instance_pre_reply_hook(
self: AgentBase,
kwargs: dict[str, Any],
) -> dict[str, Any]:
"""A pre-reply hook that modifies the message content."""
msg = kwargs["msg"]
msg.content += "[instance-pre-reply]"
# return modified kwargs
return {
**kwargs,
"msg": msg,
}
def cls_pre_reply_hook(
self: AgentBase,
kwargs: dict[str, Any],
) -> dict[str, Any]:
"""A pre-reply hook that modifies the message content."""
msg = kwargs["msg"]
msg.content += "[cls-pre-reply]"
# return modified kwargs
return {
**kwargs,
"msg": msg,
}
# Register class hook
TestAgent.register_class_hook(
hook_type="pre_reply",
hook_name="test_pre_reply",
hook=cls_pre_reply_hook,
)
# Register instance hook
agent = TestAgent()
agent.register_instance_hook(
hook_type="pre_reply",
hook_name="test_pre_reply",
hook=instance_pre_reply_hook,
)
async def example_test_hook() -> None:
"""An example function to test the hooks."""
msg = Msg(
name="user",
content="Hello, world!",
role="user",
)
res = await agent(msg)
print("Response content:", res.content)
TestAgent.clear_class_hooks()
asyncio.run(example_test_hook())
Response content: Hello, world![instance-pre-reply][cls-pre-reply]
我们可以看到消息内容中添加了“[instance-pre-reply]”和“[cls-pre-reply]”。
脚本总运行时间: (0 分 0.002 秒)