内置不够用时,你就得写自己的 Agent。最常见动机:你想在回复前强制做某件事(比如查权限、打标签),或回复后强制改写(比如脱敏)。这些都靠继承 ConversableAgent 覆盖 generate_reply 实现。
generate_reply 接收收到的消息列表,返回 (final, reply)。final 为 True 表示这就是最终回复;返回 (False, None) 则交回默认逻辑(调模型)。
from autogen import ConversableAgent import os cfg = {"model": "gpt-4o-mini", "api_key": os.environ.get("OPENAI_API_KEY")} class TaggedAgent(ConversableAgent): def generate_reply(self, messages, sender=None, **kwargs): # 自定义:所有回复前加一个标签 final, reply = super().generate_reply(messages, sender, **kwargs) if final and isinstance(reply, dict): content = reply.get("content", "") reply["content"] = f"[内部] {content}" return final, reply agent = TaggedAgent("tagged", llm_config=cfg, system_message="你是助手。") # 该 agent 的每条回复都会带上 [内部] 前缀,方便下游识别来源
如果你不想建子类,只是想插一段逻辑,register_reply 更省事(第二章 2.3 用过)。区别:子类适合"这个 Agent 本质就不同",钩子适合"偶尔插一下"。
from autogen import ConversableAgent cfg = {"model": "gpt-4o-mini", "api_key": os.environ.get("OPENAI_API_KEY")} guard = ConversableAgent("guard", llm_config=cfg, system_message="你守门。") def deny_sensitive(recipient, messages, sender, config): if "密码" in str(messages[-1].get("content","")): return True, "该请求被安全策略拦截。" return False, None guard.register_reply(trigger=ConversableAgent, reply_func=deny_sensitive, position=0)
我们经验:80% 的定制用钩子就够,子类留给"这个 Agent 的回复机制本身不同"的情况,比如它根本不调模型、而是查知识库。
下面这个 Agent 不调模型,收到问题直接查本地字典返回,适合做"事实型应答",又快又便宜。
class FaqAgent(ConversableAgent): faqs = {"退款政策": "七天内无理由。", "运费": "满99包邮。"} def generate_reply(self, messages, sender=None, **kwargs): q = str(messages[-1].get("content","")) for k, v in self.faqs.items(): if k in q: return True, v return True, "未收录该问题。" faq = FaqAgent("faq", llm_config=None, system_message="你答 FAQ。") # 不耗任何模型调用,纯查表,延迟极低
下面这个 Agent 不调模型,收到问题直接查内部服务返回,适合做"事实应答"。注意它 llm_config=None,零模型成本。
class ServiceAgent(ConversableAgent): def __init__(self, name, client, **kw): super().__init__(name, llm_config=None, **kw) self.client = client def generate_reply(self, messages, sender=None, **kwargs): q = str(messages[-1].get("content","")) # 直接查服务,不耗模型 return True, self.client.lookup(q) # 配合 5.5 的客户端对象,这个 Agent 就成了廉价的查询入口
这些坑都源于"以为覆盖了就完全自己管"。记住:覆盖是为了增强,不是取代,大多数情况先 super 再加工最稳。
一句话收尾:行为贯穿一生用子类,偶尔插一下用钩子。这个判断能避免你要么全写子类(代码膨胀),要么全用钩子(逻辑散落)。我们团队约定:新建 Agent 类型才写子类,既有类型加能力用 register_reply。
⚠️ 子类里忘了调 super().generate_reply 会导致模型能力丢失,回到空回复。
💡 自定义 Agent 是"对话即编排"里你定义角色行为的最后一道口子,用得准系统就稳。