ag2-observers-and-alerts
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseObservers, watches, and alerts
观察者、监视器与告警
When to use
适用场景
- Observability — log model responses, tool calls, token usage.
- Runtime safety — block dangerous tool arguments, halt the agent.
- Reactive metrics — fire on every Nth response, or every M seconds.
- Loop / repetition detection — catch infinite tool-call loops.
- Stateful monitoring — anything that needs to remember prior events to decide what to do next.
- 可观测性 — 记录模型响应、工具调用、Token使用情况。
- 运行时安全 — 拦截危险的工具参数、终止Agent运行。
- 响应式指标 — 每N次响应或每M秒触发一次动作。
- 循环/重复检测 — 捕获无限工具调用循环。
- 有状态监控 — 需要记录历史事件以决定后续操作的场景。
Two observer shapes
两种观察者类型
| Shape | When | Use |
|---|---|---|
| Stateless function | One-off event hook (logging, metrics) | |
| Stateful class | Counters / windows / thresholds / composed triggers | Subclass |
Both are stream subscribers under the hood — registered on the agent rather than directly on the stream.
| 类型 | 适用场景 | 使用方式 |
|---|---|---|
| 无状态函数 | 一次性事件钩子(日志、指标) | |
| 有状态类 | 计数器/时间窗口/阈值/组合触发器 | 继承 |
两者本质都是流订阅者——注册在Agent上而非直接注册在流上。
60-second recipe — @observer
@observer60秒快速上手——@observer
@observerpython
from ag2 import Agent, observer
from ag2.config import OpenAIConfig
from ag2.events import ModelResponse
@observer(ModelResponse)
async def log_response(event: ModelResponse) -> None:
print(f"Model said: {event.content}")
agent = Agent(
"assistant",
config=OpenAIConfig(model="gpt-4o-mini"),
observers=[log_response],
)Or attach after construction with . Per-call observers also supported ().
@agent.observer(...)agent.ask("...", observers=[...])Observer callbacks support full dependency injection (, , , ). Filter by event type, multiple types (), or field value (). Use to modify or suppress events before regular subscribers see them.
ContextInjectVariableDependsModelRequest | ModelResponseToolCallEvent.name == "search"interrupt=Truepython
from ag2 import Agent, observer
from ag2.config import OpenAIConfig
from ag2.events import ModelResponse
@observer(ModelResponse)
async def log_response(event: ModelResponse) -> None:
print(f"Model said: {event.content}")
agent = Agent(
"assistant",
config=OpenAIConfig(model="gpt-4o-mini"),
observers=[log_response],
)也可在Agent创建后通过附加观察者。同时支持单次调用级别的观察者()。
@agent.observer(...)agent.ask("...", observers=[...])观察者回调支持完整的依赖注入(、、、)。可按事件类型、多种类型()或字段值()过滤事件。使用可在常规订阅者看到事件前修改或抑制事件。
ContextInjectVariableDependsModelRequest | ModelResponseToolCallEvent.name == "search"interrupt=TrueBuilt-in stateful observers
内置有状态观察者
python
from ag2 import Agent
from ag2.observers import LoopDetector, TokenMonitor
agent = Agent(
"assistant",
config=config,
observers=[
TokenMonitor(warn_threshold=50_000, alert_threshold=100_000),
LoopDetector(window_size=10, repeat_threshold=3),
],
)- — tracks cumulative tokens across
TokenMonitorandModelResponse. EmitsTaskCompleted/WARNINGCRITICALs as thresholds are crossed. Read state viaObserverAlert.monitor.total_tokens - — sliding window of recent tool calls. Emits a
LoopDetectoralert whenWARNINGconsecutive identical calls are seen.repeat_threshold
python
from ag2 import Agent
from ag2.observers import LoopDetector, TokenMonitor
agent = Agent(
"assistant",
config=config,
observers=[
TokenMonitor(warn_threshold=50_000, alert_threshold=100_000),
LoopDetector(window_size=10, repeat_threshold=3),
],
)- — 追踪
TokenMonitor和ModelResponse事件的累计Token消耗。当超过阈值时发出TaskCompleted/WARNING级别的CRITICAL告警。可通过ObserverAlert读取当前状态。monitor.total_tokens - — 维护最近工具调用的滑动窗口。当连续出现
LoopDetector次相同调用时发出repeat_threshold告警。WARNING
Custom BaseObserver
BaseObserver自定义BaseObserver
BaseObserverA pairs a (when to fire) with a method (what to do):
BaseObserverWatchprocess()python
from ag2 import Context
from ag2.observers import BaseObserver
from ag2.watch import CadenceWatch
from ag2.events import BaseEvent, ModelResponse
from ag2.events.alert import ObserverAlert, Severity
class AvgCompletionObserver(BaseObserver):
"""Every N responses, emit an INFO alert with avg completion-token count."""
def __init__(self, window: int = 5) -> None:
super().__init__("avg-completion", watch=CadenceWatch(n=window, condition=ModelResponse))
self._window = window
async def process(self, events: list[BaseEvent], ctx: Context) -> ObserverAlert | None:
tokens = [e.usage.completion_tokens for e in events if isinstance(e, ModelResponse) and e.usage]
if not tokens:
return None
return ObserverAlert(
source=self.name,
severity=Severity.INFO,
message=f"Avg completion tokens over last {self._window}: {sum(tokens) / len(tokens):.0f}",
)If returns an , the base class emits it onto the stream. You can also send events manually via .
process()ObserverAlertawait ctx.send(...)BaseObserverWatchprocess()python
from ag2 import Context
from ag2.observers import BaseObserver
from ag2.watch import CadenceWatch
from ag2.events import BaseEvent, ModelResponse
from ag2.events.alert import ObserverAlert, Severity
class AvgCompletionObserver(BaseObserver):
"""每N次响应,发出包含平均生成Token数的INFO级告警。"""
def __init__(self, window: int = 5) -> None:
super().__init__("avg-completion", watch=CadenceWatch(n=window, condition=ModelResponse))
self._window = window
async def process(self, events: list[BaseEvent], ctx: Context) -> ObserverAlert | None:
tokens = [e.usage.completion_tokens for e in events if isinstance(e, ModelResponse) and e.usage]
if not tokens:
return None
return ObserverAlert(
source=self.name,
severity=Severity.INFO,
message=f"最近{self._window}次响应的平均生成Token数: {sum(tokens) / len(tokens):.0f}",
)如果返回,基类会将其发送到流中。你也可以通过手动发送事件。
process()ObserverAlertawait ctx.send(...)Watch primitives — picking when to fire
Watch原语——选择触发时机
| You need | Use |
|---|---|
| Every matching event | |
| Every N matching events | |
| Every T seconds (buffered events) | |
| Either threshold | |
| Once after delay | |
| Periodic timer | |
| Cron schedule | |
| All sub-watches must fire | |
| Any sub-watch fires | |
| In order | |
All importable from . Callback signature is uniform: . Time-driven watches pass .
ag2.watchasync def cb(events: list[BaseEvent], ctx: Context) -> Noneevents=[]| 需求 | 使用方式 |
|---|---|
| 每次匹配事件触发 | |
| 每N次匹配事件触发 | |
| 每T秒触发一次(缓冲事件) | |
| 满足任一阈值触发 | |
| 延迟一段时间后触发一次 | |
| 周期性定时触发 | |
| Cron调度触发 | |
| 所有子监视器都触发才执行 | |
| 任一子监视器触发即执行 | |
| 按顺序触发 | |
所有原语均可从导入。回调签名统一为:。时间驱动的监视器会传入。
ag2.watchasync def cb(events: list[BaseEvent], ctx: Context) -> Noneevents=[]ObserverAlert
— the alert type
ObserverAlertObserverAlert
——告警类型
ObserverAlertpython
from ag2.events.alert import ObserverAlert, Severity
ObserverAlert(
source="my-observer",
severity=Severity.WARNING, # INFO, WARNING, CRITICAL, FATAL
message="What happened",
)Important: is on the stream and persisted in history, but the default provider mappers do not render it back to the LLM. To make the agent see alerts, add to :
ObserverAlertAlertPolicy()assembly=[...]python
from ag2.policies import AlertPolicy
agent = Agent("assistant", config=config, assembly=[AlertPolicy()])python
from ag2.events.alert import ObserverAlert, Severity
ObserverAlert(
source="my-observer",
severity=Severity.WARNING, # INFO, WARNING, CRITICAL, FATAL
message="What happened",
)重要提示:会被发送到流中并保存在历史记录中,但默认的提供者映射不会将其返回给LLM。要让Agent看到告警,需将添加到中:
ObserverAlertAlertPolicy()assembly=[...]python
from ag2.policies import AlertPolicy
agent = Agent("assistant", config=config, assembly=[AlertPolicy()])FATAL alerts → HaltEvent
→ short-circuit
HaltEventFATAL告警 → HaltEvent
→ 短路终止
HaltEventAlertPolicySeverity.FATAL- Emits a on the stream.
HaltEvent - Appends a halt notice to the system prompt.
When is non-empty, the harness automatically wires which sees the and short-circuits the next LLM call with a synthetic response.
assembly=[...]_HaltCheckMiddlewareHaltEventHALTED: ...python
from ag2 import Context
from ag2.observers import BaseObserver
from ag2.events import BaseEvent, ToolCallEvent
from ag2.events.alert import HaltEvent, ObserverAlert, Severity
from ag2.policies import AlertPolicy
from ag2.watch import EventWatch
class PathGuardian(BaseObserver):
def __init__(self) -> None:
super().__init__("path-guardian", watch=EventWatch(ToolCallEvent))
async def process(self, events: list[BaseEvent], ctx: Context) -> ObserverAlert | None:
for event in events:
if not isinstance(event, ToolCallEvent) or event.name != "write_file":
continue
if "/etc/" in event.arguments or "/usr/" in event.arguments:
return ObserverAlert(
source=self.name,
severity=Severity.FATAL,
message=f"blocked dangerous write: {event.arguments}",
)
return None
agent = Agent(
"safe-shell",
prompt="...",
config=config,
tools=[write_file],
observers=[PathGuardian()],
assembly=[AlertPolicy()], # routes FATAL → HaltEvent
)The first dangerous tool call triggers FATAL → halt; the agent's next ask is short-circuited. Full runnable demo: .
assets/safety_guard.pyAlertPolicySeverity.FATAL- 向流中发送事件。
HaltEvent - 在系统提示中追加终止通知。
当非空时,执行器会自动连接,该中间件会检测到并通过合成的响应短路下一次LLM调用。
assembly=[...]_HaltCheckMiddlewareHaltEventHALTED: ...python
from ag2 import Context
from ag2.observers import BaseObserver
from ag2.events import BaseEvent, ToolCallEvent
from ag2.events.alert import HaltEvent, ObserverAlert, Severity
from ag2.policies import AlertPolicy
from ag2.watch import EventWatch
class PathGuardian(BaseObserver):
def __init__(self) -> None:
super().__init__("path-guardian", watch=EventWatch(ToolCallEvent))
async def process(self, events: list[BaseEvent], ctx: Context) -> ObserverAlert | None:
for event in events:
if not isinstance(event, ToolCallEvent) or event.name != "write_file":
continue
if "/etc/" in event.arguments or "/usr/" in event.arguments:
return ObserverAlert(
source=self.name,
severity=Severity.FATAL,
message=f"blocked dangerous write: {event.arguments}",
)
return None
agent = Agent(
"safe-shell",
prompt="...",
config=config,
tools=[write_file],
observers=[PathGuardian()],
assembly=[AlertPolicy()], # 将FATAL告警路由为HaltEvent
)首次危险工具调用会触发FATAL告警并终止运行;Agent的下一次请求会被短路。完整可运行示例:。
assets/safety_guard.pySubscribing to alerts and halts from outside
从外部订阅告警与终止事件
python
from ag2 import MemoryStream
from ag2.events.alert import HaltEvent, ObserverAlert
stream = MemoryStream()
stream.where(ObserverAlert).subscribe(lambda e: print(f"[{e.severity}] {e.source}: {e.message}"))
stream.where(HaltEvent).subscribe(lambda e: print(f"HALT: {e.reason}"))
await agent.ask("...", stream=stream)python
from ag2 import MemoryStream
from ag2.events.alert import HaltEvent, ObserverAlert
stream = MemoryStream()
stream.where(ObserverAlert).subscribe(lambda e: print(f"[{e.severity}] {e.source}: {e.message}"))
stream.where(HaltEvent).subscribe(lambda e: print(f"HALT: {e.reason}"))
await agent.ask("...", stream=stream)Observers vs Middleware vs Stream subscribers
Observer vs Middleware vs Stream订阅者
| Feature | Observer | Middleware | Stream subscriber |
|---|---|---|---|
| Registered on | Agent | Agent | Stream |
| Lifecycle | Scoped to execution | Scoped to execution | Manual |
| Boilerplate | Function (or | | Function |
| Can modify events | | Yes (wraps execution) | |
| DI support | Yes | Yes | Yes |
| Use case | Monitoring, metrics, alerts | Cross-cutting (retry, auth, rate limit) | Low-level event wiring |
| 特性 | Observer | Middleware | Stream 订阅者 |
|---|---|---|---|
| 注册位置 | Agent | Agent | Stream |
| 生命周期 | 作用于执行阶段 | 作用于执行阶段 | 手动控制 |
| 模板代码 | 函数(或 | | 函数 |
| 能否修改事件 | | 可以(包装执行流程) | |
| 依赖注入支持 | 是 | 是 | 是 |
| 适用场景 | 监控、指标、告警 | 横切关注点(重试、认证、限流) | 底层事件连接 |
Going deeper
深入学习
- — three observers (
assets/token_watchdog.py,TokenMonitor, customLoopDetector) on one agent. MirrorsAlertConsole.code_examples/04 - —
assets/safety_guard.py→ FATAL →PathGuardian→AlertPolicy→ short-circuit. MirrorsHaltEvent.code_examples/08 - Source docs:
- —
website/docs/user-guide/advanced/observers.mdx,@observer, registration, built-ins,BaseObserver.ObserverAlert - — every Watch primitive, composition rules.
website/docs/user-guide/advanced/watches.mdx - — Stream API,
website/docs/user-guide/advanced/stream.mdx,where, interrupters,subscribe.RedisStream - —
website/docs/user-guide/advanced/assembly.mdxordering and dedup.AlertPolicy
- — 一个Agent上同时使用三个观察者(
assets/token_watchdog.py、TokenMonitor、自定义LoopDetector)。对应AlertConsole。code_examples/04 - —
assets/safety_guard.py→ FATAL告警 →PathGuardian→AlertPolicy→ 短路终止。对应HaltEvent。code_examples/08 - 源码文档:
- —
website/docs/user-guide/advanced/observers.mdx、@observer、注册方式、内置组件、BaseObserver。ObserverAlert - — 所有Watch原语、组合规则。
website/docs/user-guide/advanced/watches.mdx - — Stream API、
website/docs/user-guide/advanced/stream.mdx、where、拦截器、subscribe。RedisStream - —
website/docs/user-guide/advanced/assembly.mdx的排序与去重。AlertPolicy
Common pitfalls
常见误区
- Alerts not reaching the model — events are on the stream but invisible to the LLM by default. Add
ObserverAlerttoAlertPolicy().assembly=[...] - FATAL not halting — is what creates
AlertPolicy. WithoutHaltEvent(or any non-empty assembly chain enablingassembly=[..., AlertPolicy(), ...]), nothing halts._HaltCheckMiddleware - Sharing one across agents — dedup state lives on the instance. Give each agent its own.
AlertPolicy() - Watch callback assumes is non-empty — for time-driven watches (
events,DelayWatch,IntervalWatch),CronWatchis alwaysevents.[] - Forgetting is async —
process()must beBaseObserver.process.async def - Subscribing with when you wanted
subscribe(fn)decorator — both work; the bare-call form issubscribe(), the decorator form isstream.subscribe(fn)(with parens).@stream.subscribe() - with no
CadenceWatchand non— raisesmax_wait; at least one is required.ValueError
- 告警未传递给模型 — 事件存在于流中,但默认对LLM不可见。需将
ObserverAlert添加到AlertPolicy()中。assembly=[...] - FATAL告警未触发终止 — 是生成
AlertPolicy的关键。如果没有HaltEvent(或任何非空的执行链启用assembly=[..., AlertPolicy(), ...]),则不会触发终止。_HaltCheckMiddleware - 多个Agent共享同一个实例 — 去重状态存储在实例中。应为每个Agent创建独立的实例。
AlertPolicy() - Watch回调假设非空 — 对于时间驱动的监视器(
events、DelayWatch、IntervalWatch),CronWatch始终为events。[] - 忘记是异步函数 —
process()必须定义为BaseObserver.process。async def - 使用而非装饰器形式
subscribe(fn)— 两种方式都可行;直接调用形式为subscribe(),装饰器形式为stream.subscribe(fn)(带括号)。@stream.subscribe() - 未设置
CadenceWatch或n— 会抛出max_wait;至少需要设置其中一个参数。ValueError