mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-08-13 01:38:41 +00:00
fix: emit FlowStartedEvent when a boundary hook aborts the flow (#6953)
* fix: emit FlowStartedEvent when a boundary hook aborts the flow A HookAborted at EXECUTION_START or INPUT propagated before `FlowStartedEvent` was emitted, so a policy deny left logs but no record of the execution. On abort, stamp the state id and open the flow scope before re-raising: the deny surfaces as a started -> failed execution while normal runs keep the existing ordering — the started event carries hook-resolved inputs and `id` rewrites keep redirecting persistence restoration. * docs: translate execution-boundary-hooks page to ar, ko, and pt-BR The English page updated on this branch had never been localized. Translate it into the three supported locales following `DOCS_TRANSLATIONS.md` and register the page in each locale's navigation in `docs/docs.json`. Untranslated link targets (the step-hooks page and the aborting-an-operation anchor) are omitted rather than pointed at English, matching the locale navigation convention.
This commit is contained in:
200
docs/edge/ar/learn/execution-boundary-hooks.mdx
Normal file
200
docs/edge/ar/learn/execution-boundary-hooks.mdx
Normal file
@@ -0,0 +1,200 @@
|
||||
---
|
||||
title: خطافات حدود التنفيذ
|
||||
description: اعتراض بداية تنفيذ الـ Crew والـ Flow ومدخلاته ومخرجاته ونهايته باستخدام المزخرف @on
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
تعترض خطافات حدود التنفيذ الأطراف الخارجية للتشغيل — قبل بدء أي عمل، وعند
|
||||
حسم المدخلات، وعند جاهزية النتيجة النهائية، وعند انتهاء التنفيذ. وهي تعمل مع
|
||||
الـ Crew والـ Flow على حد سواء، وتُعد المكان المناسب لفحوصات السياسة على
|
||||
مستوى التشغيل وإعادة كتابة المدخلات وتنقية المخرجات.
|
||||
|
||||
## نظرة عامة
|
||||
|
||||
أربع نقاط اعتراض تغطي الحدود:
|
||||
|
||||
| النقطة | التوقيت | `ctx.payload` |
|
||||
|--------|---------|---------------|
|
||||
| `EXECUTION_START` | Crew أو Flow على وشك البدء | `dict` المدخلات |
|
||||
| `INPUT` | المدخلات المحسومة للتنفيذ | `dict` المدخلات |
|
||||
| `OUTPUT` | النتيجة النهائية جاهزة | كائن المخرجات |
|
||||
| `EXECUTION_END` | انتهى التنفيذ (نجاحًا أو فشلًا) | كائن المخرجات، أو `None` عند الفشل |
|
||||
|
||||
بالنسبة إلى الـ Crew، يكون payload المخرجات `CrewOutput`. أما في الـ Flow فهو
|
||||
النتيجة النهائية لدالة الـ Flow.
|
||||
|
||||
## توقيع الخطاف
|
||||
|
||||
```python
|
||||
from crewai.hooks import on, HookAborted, InterceptionPoint
|
||||
|
||||
@on(InterceptionPoint.EXECUTION_START)
|
||||
def boundary_hook(ctx) -> Any | None:
|
||||
# Mutate ctx.payload in place, or
|
||||
# return a non-None value to replace it, or
|
||||
# raise HookAborted(reason, source) to stop the run
|
||||
return None
|
||||
```
|
||||
|
||||
تتبع خطافات الحدود العقد القياسي: المتابعة (`return None`)، أو التعديل في
|
||||
المكان، أو الاستبدال بإرجاع قيمة، أو الإجهاض برفع `HookAborted`. أي إجهاض
|
||||
عند أي حد ينتشر خارج `kickoff()` مع سببه.
|
||||
|
||||
## مخطط السياق
|
||||
|
||||
تتلقى كل نقطة سياقًا منمّطًا. تشترك جميع السياقات في الحقول الأساسية:
|
||||
|
||||
```python
|
||||
class InterceptionContext:
|
||||
payload: Any # The interceptable value (see table above)
|
||||
agent: Any = None # Not populated at execution boundaries
|
||||
agent_role: str | None # Not populated at execution boundaries
|
||||
task: Any = None # Not populated at execution boundaries
|
||||
crew: Any = None # The Crew instance (crew runs only)
|
||||
flow: Any = None # The Flow instance (flow runs only)
|
||||
```
|
||||
|
||||
تضيف سياقات كل نقطة اسمًا بديلًا للـ payload:
|
||||
|
||||
```python
|
||||
class ExecutionStartContext(InterceptionContext):
|
||||
inputs: dict # Same dict as payload
|
||||
|
||||
class InputContext(InterceptionContext):
|
||||
inputs: dict # Same dict as payload
|
||||
|
||||
class OutputContext(InterceptionContext):
|
||||
output: Any # The output object
|
||||
|
||||
class ExecutionEndContext(InterceptionContext):
|
||||
output: Any # The output object (None when status == "failed")
|
||||
status: str # "completed" or "failed"
|
||||
error: BaseException | None # The exception when status == "failed"
|
||||
```
|
||||
|
||||
<Note>
|
||||
`ctx.inputs` هو اسم بديل لقاموس المدخلات **الأصلي**، لذا فإن التعديلات في
|
||||
المكان عبر أي من الاسمين متكافئة. إذا *استبدل* خطاف سابق الـ payload بإرجاع
|
||||
dict جديد، فإن `ctx.payload` وحده يُعاد ربطه — اقرأ واكتب دائمًا عبر
|
||||
`ctx.payload` عندما يمكن أن تتسلسل الخطافات.
|
||||
</Note>
|
||||
|
||||
## تشغيلات الـ Crew مقابل تشغيلات الـ Flow
|
||||
|
||||
تعمل خطافات الحدود على كلا وقتي التشغيل، وتنفيذ الـ Crew يجري داخليًا فوق وقت
|
||||
تشغيل Flow. لذلك أثناء `crew.kickoff()` يُطلق الخطاف الحدودي العام لحدّ الـ
|
||||
Crew (`ctx.crew` مضبوط و`ctx.flow` يساوي `None`) **و** للـ Flow الداخلي
|
||||
(`ctx.flow` مضبوط و`ctx.crew` يساوي `None`). ميّز حسب وقت التشغيل:
|
||||
|
||||
```python
|
||||
@on(InterceptionPoint.OUTPUT)
|
||||
def crew_output_only(ctx):
|
||||
if ctx.crew is None:
|
||||
return None # Skip the internal flow (or a bare flow)
|
||||
ctx.payload.raw = ctx.payload.raw.strip()
|
||||
```
|
||||
|
||||
## حالات استخدام شائعة
|
||||
|
||||
### فحص السياسة عند البدء
|
||||
|
||||
```python
|
||||
@on(InterceptionPoint.EXECUTION_START)
|
||||
def enforce_policy(ctx):
|
||||
if ctx.crew is not None and not ctx.payload.get("authorized"):
|
||||
raise HookAborted(reason="unauthorized execution", source="access-control")
|
||||
```
|
||||
|
||||
### إعادة كتابة المدخلات
|
||||
|
||||
```python
|
||||
@on(InterceptionPoint.INPUT)
|
||||
def add_defaults(ctx):
|
||||
if ctx.crew is None:
|
||||
return None
|
||||
ctx.payload.setdefault("locale", "en-US")
|
||||
ctx.payload["topic"] = ctx.payload["topic"].strip().lower()
|
||||
```
|
||||
|
||||
تتدفق المدخلات المعاد كتابتها إلى استيفاء الـ Task، فيتصرف التشغيل كما لو
|
||||
بدأ بالقاموس المعدل.
|
||||
|
||||
فضّل `INPUT` لإعادة الكتابة وعامل `EXECUTION_START` كبوابة سماح/منع. إعادة
|
||||
الكتابة عند `EXECUTION_START` تظل مُحترمة — في الـ Crew تغذي أيضًا استدعاءات
|
||||
`before_kickoff`؛ وفي الـ Flow تُطبق تمامًا كإعادة كتابة `INPUT`.
|
||||
|
||||
### تنقية المخرجات
|
||||
|
||||
```python
|
||||
import re
|
||||
|
||||
@on(InterceptionPoint.OUTPUT)
|
||||
def redact_emails(ctx):
|
||||
if ctx.crew is None:
|
||||
return None
|
||||
ctx.payload.raw = re.sub(
|
||||
r"\b[\w.+-]+@[\w-]+\.[\w.]+\b", "[EMAIL-REDACTED]", ctx.payload.raw
|
||||
)
|
||||
```
|
||||
|
||||
يعمل `OUTPUT` قبل `EXECUTION_END`، وكلاهما يرى الـ payload (الذي ربما
|
||||
استُبدل) من الخطافات السابقة؛ والقيمة النهائية المعاد كتابتها هي ما يعيده
|
||||
`kickoff()`.
|
||||
|
||||
### مراقبة الإخفاقات
|
||||
|
||||
يُطلق `EXECUTION_END` مرة واحدة بالضبط لكل تنفيذ، عند النجاح والفشل على حد
|
||||
سواء. عندما يرفع التشغيل استثناءً — خطأ في Task، أو استثناء في دالة Flow، أو
|
||||
`HookAborted` من نقطة سابقة — يتلقى الخطاف `status="failed"` مع الاستثناء في
|
||||
`ctx.error`، ويظل الاستثناء الأصلي ينتشر خارج `kickoff()` دون تغيير:
|
||||
|
||||
```python
|
||||
@on(InterceptionPoint.EXECUTION_END)
|
||||
def report_outcome(ctx):
|
||||
if ctx.status == "failed":
|
||||
notify_policy_engine(status="failed", error=repr(ctx.error))
|
||||
else:
|
||||
notify_policy_engine(status="completed")
|
||||
```
|
||||
|
||||
تنبيهان: لا يُطلق `EXECUTION_END` عندما لا يكون `EXECUTION_START` قد أُرسل
|
||||
أصلًا (الإجهاض عند البدء يعني أن الحد لم يُفتح قط، فلا توجد نهاية تقابله)،
|
||||
ورفع `HookAborted` من إرسال `EXECUTION_END` في مسار الفشل يُتجاهل — لم يعد
|
||||
هناك ما يُجهض، والخطأ الأصلي هو الغالب.
|
||||
|
||||
## الترتيب
|
||||
|
||||
لتشغيل Crew يكون ترتيب الحدود:
|
||||
|
||||
```
|
||||
EXECUTION_START → before_kickoff callbacks → INPUT → tasks execute → OUTPUT → EXECUTION_END
|
||||
```
|
||||
|
||||
لتشغيل Flow، تحسم خطافات الحدود المدخلات قبل أن تبدأ أحداث دورة الحياة:
|
||||
|
||||
```
|
||||
EXECUTION_START → INPUT → FlowStartedEvent → flow methods execute → OUTPUT → EXECUTION_END → FlowFinishedEvent
|
||||
```
|
||||
|
||||
يحمل `FlowStartedEvent` المدخلات كما حسمتها الخطافات، وإعادة كتابة
|
||||
`inputs["id"]` داخل خطاف حدودي تعيد توجيه استعادة الحالة. يظهر الإجهاض عند
|
||||
`EXECUTION_START` مع ذلك كحدث `FlowStartedEvent` يتبعه `FlowFailedEvent`،
|
||||
ويُبثان عند الإجهاض مع الحمولة كما حسمتها الخطافات التي عملت قبله.
|
||||
|
||||
تعمل الخطافات في النقطة نفسها حسب ترتيب التسجيل، الخطافات العامة أولًا ثم
|
||||
الخطافات المحدودة بالـ Crew. تُبث القياسات (`HookDispatchedEvent`) مع كل
|
||||
إرسال.
|
||||
|
||||
## إدارة الخطافات في الاختبارات
|
||||
|
||||
```python
|
||||
from crewai.hooks import clear_all_hooks
|
||||
|
||||
clear_all_hooks() # Clears every point, including boundaries
|
||||
```
|
||||
|
||||
## وثائق ذات صلة
|
||||
|
||||
- [نظرة عامة على خطافات التنفيذ →](/edge/ar/learn/execution-hooks)
|
||||
- [خطافات استدعاء LLM →](/edge/ar/learn/llm-hooks)
|
||||
- [خطافات استدعاء الأدوات →](/edge/ar/learn/tool-hooks)
|
||||
@@ -120,6 +120,11 @@ def add_defaults(ctx):
|
||||
Rewritten inputs flow into task interpolation, so the run behaves as if it was
|
||||
kicked off with the modified dict.
|
||||
|
||||
Prefer `INPUT` for rewriting and treat `EXECUTION_START` as the allow/deny
|
||||
gate. Rewrites at `EXECUTION_START` are still honored — on crews they also
|
||||
feed the `before_kickoff` callbacks; on flows they land exactly like an
|
||||
`INPUT` rewrite.
|
||||
|
||||
### Output Sanitization
|
||||
|
||||
```python
|
||||
@@ -156,9 +161,10 @@ def report_outcome(ctx):
|
||||
```
|
||||
|
||||
Two caveats: `EXECUTION_END` does not fire when `EXECUTION_START` never
|
||||
dispatched (an abort at start counts as the execution never beginning), and
|
||||
raising `HookAborted` from a failure-path `EXECUTION_END` dispatch is ignored —
|
||||
there is nothing left to abort, and the original error wins.
|
||||
dispatched (an abort at start means the boundary never opened, so there is no
|
||||
end to pair), and raising `HookAborted` from a failure-path `EXECUTION_END`
|
||||
dispatch is ignored — there is nothing left to abort, and the original error
|
||||
wins.
|
||||
|
||||
## Ordering
|
||||
|
||||
@@ -168,6 +174,19 @@ For a crew run the boundary order is:
|
||||
EXECUTION_START → before_kickoff callbacks → INPUT → tasks execute → OUTPUT → EXECUTION_END
|
||||
```
|
||||
|
||||
For a flow run, the boundary hooks resolve the inputs before the lifecycle
|
||||
events begin:
|
||||
|
||||
```
|
||||
EXECUTION_START → INPUT → FlowStartedEvent → flow methods execute → OUTPUT → EXECUTION_END → FlowFinishedEvent
|
||||
```
|
||||
|
||||
`FlowStartedEvent` carries the hook-resolved inputs, and rewriting
|
||||
`inputs["id"]` in a boundary hook redirects state restoration. An abort at
|
||||
`EXECUTION_START` still surfaces as `FlowStartedEvent` followed by
|
||||
`FlowFailedEvent`, emitted at the abort with the payload as resolved by the
|
||||
hooks that ran before it.
|
||||
|
||||
Hooks at the same point run in registration order, global hooks first, then
|
||||
crew-scoped hooks. Telemetry (`HookDispatchedEvent`) is emitted per dispatch.
|
||||
|
||||
|
||||
204
docs/edge/ko/learn/execution-boundary-hooks.mdx
Normal file
204
docs/edge/ko/learn/execution-boundary-hooks.mdx
Normal file
@@ -0,0 +1,204 @@
|
||||
---
|
||||
title: 실행 경계 훅
|
||||
description: "@on 데코레이터로 crew와 flow 실행의 시작, 입력, 출력, 종료를 가로채기"
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
실행 경계 훅은 실행의 가장 바깥쪽 경계를 가로챕니다 — 작업이 시작되기 전,
|
||||
입력이 확정될 때, 최종 결과가 준비될 때, 그리고 실행이 끝날 때입니다. 크루와
|
||||
플로우 모두에서 발생하며, 실행 수준의 정책 검사, 입력 재작성, 출력 정제에
|
||||
적합한 위치입니다.
|
||||
|
||||
## 개요
|
||||
|
||||
네 가지 인터셉션 포인트가 경계를 담당합니다:
|
||||
|
||||
| 포인트 | 시점 | `ctx.payload` |
|
||||
|--------|------|---------------|
|
||||
| `EXECUTION_START` | 크루 또는 플로우가 막 시작되려는 시점 | 입력 `dict` |
|
||||
| `INPUT` | 실행을 위한 입력이 확정된 시점 | 입력 `dict` |
|
||||
| `OUTPUT` | 최종 결과가 준비된 시점 | 출력 객체 |
|
||||
| `EXECUTION_END` | 실행이 끝난 시점(성공 또는 실패) | 출력 객체, 실패 시 `None` |
|
||||
|
||||
크루의 경우 출력 payload는 `CrewOutput`입니다. 플로우의 경우 최종 플로우
|
||||
메서드의 결과입니다.
|
||||
|
||||
## 훅 시그니처
|
||||
|
||||
```python
|
||||
from crewai.hooks import on, HookAborted, InterceptionPoint
|
||||
|
||||
@on(InterceptionPoint.EXECUTION_START)
|
||||
def boundary_hook(ctx) -> Any | None:
|
||||
# Mutate ctx.payload in place, or
|
||||
# return a non-None value to replace it, or
|
||||
# raise HookAborted(reason, source) to stop the run
|
||||
return None
|
||||
```
|
||||
|
||||
경계 훅은 표준 계약을 따릅니다: 진행(`return None`), 제자리(in-place) 수정,
|
||||
값을 반환하여 교체, 또는 `HookAborted`를 발생시켜 중단합니다. 어떤
|
||||
경계에서든 중단(abort)은 그 사유와 함께 `kickoff()` 밖으로 전파됩니다.
|
||||
|
||||
## 컨텍스트 스키마
|
||||
|
||||
각 포인트는 타입이 지정된 컨텍스트를 받습니다. 모든 컨텍스트는 공통 기본
|
||||
필드를 공유합니다:
|
||||
|
||||
```python
|
||||
class InterceptionContext:
|
||||
payload: Any # The interceptable value (see table above)
|
||||
agent: Any = None # Not populated at execution boundaries
|
||||
agent_role: str | None # Not populated at execution boundaries
|
||||
task: Any = None # Not populated at execution boundaries
|
||||
crew: Any = None # The Crew instance (crew runs only)
|
||||
flow: Any = None # The Flow instance (flow runs only)
|
||||
```
|
||||
|
||||
포인트별 컨텍스트는 payload에 대한 이름 있는 별칭을 추가합니다:
|
||||
|
||||
```python
|
||||
class ExecutionStartContext(InterceptionContext):
|
||||
inputs: dict # Same dict as payload
|
||||
|
||||
class InputContext(InterceptionContext):
|
||||
inputs: dict # Same dict as payload
|
||||
|
||||
class OutputContext(InterceptionContext):
|
||||
output: Any # The output object
|
||||
|
||||
class ExecutionEndContext(InterceptionContext):
|
||||
output: Any # The output object (None when status == "failed")
|
||||
status: str # "completed" or "failed"
|
||||
error: BaseException | None # The exception when status == "failed"
|
||||
```
|
||||
|
||||
<Note>
|
||||
`ctx.inputs`는 **원본** 입력 dict의 별칭이므로, 어느 이름으로든 제자리
|
||||
수정은 동일하게 동작합니다. 이전 훅이 새 dict를 반환하여 payload를
|
||||
*교체*했다면 `ctx.payload`만 다시 바인딩됩니다 — 훅이 연쇄될 수 있는 경우
|
||||
항상 `ctx.payload`를 읽고 쓰세요.
|
||||
</Note>
|
||||
|
||||
## 크루 실행 vs. 플로우 실행
|
||||
|
||||
경계 훅은 두 런타임 모두에서 발생하며, 크루 실행은 내부적으로 플로우 런타임
|
||||
위에서 동작합니다. 따라서 `crew.kickoff()` 중에는 전역 경계 훅이 크루
|
||||
경계(`ctx.crew` 설정, `ctx.flow`는 `None`)**와** 내부 플로우(`ctx.flow`
|
||||
설정, `ctx.crew`는 `None`) 모두에서 발생합니다. 런타임으로 구분하세요:
|
||||
|
||||
```python
|
||||
@on(InterceptionPoint.OUTPUT)
|
||||
def crew_output_only(ctx):
|
||||
if ctx.crew is None:
|
||||
return None # Skip the internal flow (or a bare flow)
|
||||
ctx.payload.raw = ctx.payload.raw.strip()
|
||||
```
|
||||
|
||||
## 일반적인 사용 사례
|
||||
|
||||
### 시작 시 정책 검사
|
||||
|
||||
```python
|
||||
@on(InterceptionPoint.EXECUTION_START)
|
||||
def enforce_policy(ctx):
|
||||
if ctx.crew is not None and not ctx.payload.get("authorized"):
|
||||
raise HookAborted(reason="unauthorized execution", source="access-control")
|
||||
```
|
||||
|
||||
### 입력 재작성
|
||||
|
||||
```python
|
||||
@on(InterceptionPoint.INPUT)
|
||||
def add_defaults(ctx):
|
||||
if ctx.crew is None:
|
||||
return None
|
||||
ctx.payload.setdefault("locale", "en-US")
|
||||
ctx.payload["topic"] = ctx.payload["topic"].strip().lower()
|
||||
```
|
||||
|
||||
재작성된 입력은 태스크 보간(interpolation)으로 흘러가므로, 실행은 수정된
|
||||
dict로 시작된 것처럼 동작합니다.
|
||||
|
||||
재작성에는 `INPUT`을 사용하고, `EXECUTION_START`는 허용/거부 게이트로
|
||||
취급하세요. `EXECUTION_START`에서의 재작성도 여전히 반영됩니다 — 크루에서는
|
||||
`before_kickoff` 콜백에도 전달되고, 플로우에서는 `INPUT` 재작성과 동일하게
|
||||
적용됩니다.
|
||||
|
||||
### 출력 정제
|
||||
|
||||
```python
|
||||
import re
|
||||
|
||||
@on(InterceptionPoint.OUTPUT)
|
||||
def redact_emails(ctx):
|
||||
if ctx.crew is None:
|
||||
return None
|
||||
ctx.payload.raw = re.sub(
|
||||
r"\b[\w.+-]+@[\w-]+\.[\w.]+\b", "[EMAIL-REDACTED]", ctx.payload.raw
|
||||
)
|
||||
```
|
||||
|
||||
`OUTPUT`은 `EXECUTION_END`보다 먼저 실행되며, 둘 다 이전 훅에서 (교체되었을
|
||||
수 있는) payload를 봅니다. 최종적으로 재작성된 값이 `kickoff()`가 반환하는
|
||||
값입니다.
|
||||
|
||||
### 실패 관찰
|
||||
|
||||
`EXECUTION_END`는 성공이든 실패든 실행마다 정확히 한 번 발생합니다. 실행이
|
||||
예외를 던지면 — 태스크 오류, 플로우 메서드 예외, 또는 이전 포인트의
|
||||
`HookAborted` — 훅은 `ctx.error`에 예외가 담긴 `status="failed"`를 받으며,
|
||||
원래 예외는 변경 없이 `kickoff()` 밖으로 전파됩니다:
|
||||
|
||||
```python
|
||||
@on(InterceptionPoint.EXECUTION_END)
|
||||
def report_outcome(ctx):
|
||||
if ctx.status == "failed":
|
||||
notify_policy_engine(status="failed", error=repr(ctx.error))
|
||||
else:
|
||||
notify_policy_engine(status="completed")
|
||||
```
|
||||
|
||||
두 가지 주의 사항: `EXECUTION_START`가 디스패치되지 않았다면
|
||||
`EXECUTION_END`는 발생하지 않습니다(시작 시점의 중단은 경계가 열리지
|
||||
않았다는 뜻이므로 짝을 이룰 종료가 없습니다). 또한 실패 경로의
|
||||
`EXECUTION_END` 디스패치에서 `HookAborted`를 발생시키는 것은 무시됩니다 —
|
||||
더 이상 중단할 것이 없고, 원래 오류가 우선합니다.
|
||||
|
||||
## 순서
|
||||
|
||||
크루 실행의 경계 순서는 다음과 같습니다:
|
||||
|
||||
```
|
||||
EXECUTION_START → before_kickoff callbacks → INPUT → tasks execute → OUTPUT → EXECUTION_END
|
||||
```
|
||||
|
||||
플로우 실행에서는 라이프사이클 이벤트가 시작되기 전에 경계 훅이 입력을
|
||||
확정합니다:
|
||||
|
||||
```
|
||||
EXECUTION_START → INPUT → FlowStartedEvent → flow methods execute → OUTPUT → EXECUTION_END → FlowFinishedEvent
|
||||
```
|
||||
|
||||
`FlowStartedEvent`는 훅이 확정한 입력을 담으며, 경계 훅에서 `inputs["id"]`를
|
||||
재작성하면 상태 복원 대상이 바뀝니다. `EXECUTION_START`에서의 중단은 여전히
|
||||
`FlowStartedEvent` 다음에 `FlowFailedEvent`가 오는 형태로 나타나며, 중단
|
||||
시점에 그때까지 실행된 훅이 확정한 페이로드와 함께 발생합니다.
|
||||
|
||||
같은 포인트의 훅은 등록 순서대로 실행되며, 전역 훅이 먼저, 그다음 크루 범위
|
||||
훅이 실행됩니다. 텔레메트리(`HookDispatchedEvent`)는 디스패치마다
|
||||
발생합니다.
|
||||
|
||||
## 테스트에서 훅 관리
|
||||
|
||||
```python
|
||||
from crewai.hooks import clear_all_hooks
|
||||
|
||||
clear_all_hooks() # Clears every point, including boundaries
|
||||
```
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [실행 훅 개요 →](/edge/ko/learn/execution-hooks)
|
||||
- [LLM 호출 훅 →](/edge/ko/learn/llm-hooks)
|
||||
- [도구 호출 훅 →](/edge/ko/learn/tool-hooks)
|
||||
211
docs/edge/pt-BR/learn/execution-boundary-hooks.mdx
Normal file
211
docs/edge/pt-BR/learn/execution-boundary-hooks.mdx
Normal file
@@ -0,0 +1,211 @@
|
||||
---
|
||||
title: Hooks de Fronteira de Execução
|
||||
description: Intercepte o início, as entradas, a saída e o fim de execuções de crews e flows com o decorator @on
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
Os hooks de fronteira de execução interceptam as bordas mais externas de uma
|
||||
execução — antes de qualquer trabalho começar, quando as entradas são
|
||||
resolvidas, quando o resultado final está pronto e quando a execução termina.
|
||||
Eles disparam tanto para crews quanto para flows e são o lugar certo para
|
||||
verificações de política no nível da execução, reescrita de entradas e
|
||||
sanitização de saídas.
|
||||
|
||||
## Visão Geral
|
||||
|
||||
Quatro pontos de interceptação cobrem as fronteiras:
|
||||
|
||||
| Ponto | Quando | `ctx.payload` |
|
||||
|-------|--------|---------------|
|
||||
| `EXECUTION_START` | Uma crew ou flow está prestes a começar | `dict` de entradas |
|
||||
| `INPUT` | Entradas resolvidas para a execução | `dict` de entradas |
|
||||
| `OUTPUT` | O resultado final está pronto | o objeto de saída |
|
||||
| `EXECUTION_END` | A execução terminou (sucesso ou falha) | o objeto de saída, ou `None` em caso de falha |
|
||||
|
||||
Para uma crew, o payload de saída é um `CrewOutput`. Para um flow, é o
|
||||
resultado final do método do flow.
|
||||
|
||||
## Assinatura do Hook
|
||||
|
||||
```python
|
||||
from crewai.hooks import on, HookAborted, InterceptionPoint
|
||||
|
||||
@on(InterceptionPoint.EXECUTION_START)
|
||||
def boundary_hook(ctx) -> Any | None:
|
||||
# Mutate ctx.payload in place, or
|
||||
# return a non-None value to replace it, or
|
||||
# raise HookAborted(reason, source) to stop the run
|
||||
return None
|
||||
```
|
||||
|
||||
Hooks de fronteira seguem o contrato padrão: prosseguir (`return None`), mutar
|
||||
in place, substituir retornando um valor, ou abortar lançando `HookAborted`.
|
||||
Um abort em qualquer fronteira propaga para fora do `kickoff()` com seu
|
||||
motivo.
|
||||
|
||||
## Esquema de Contexto
|
||||
|
||||
Cada ponto recebe um contexto tipado. Todos os contextos compartilham os
|
||||
campos base:
|
||||
|
||||
```python
|
||||
class InterceptionContext:
|
||||
payload: Any # The interceptable value (see table above)
|
||||
agent: Any = None # Not populated at execution boundaries
|
||||
agent_role: str | None # Not populated at execution boundaries
|
||||
task: Any = None # Not populated at execution boundaries
|
||||
crew: Any = None # The Crew instance (crew runs only)
|
||||
flow: Any = None # The Flow instance (flow runs only)
|
||||
```
|
||||
|
||||
Os contextos de cada ponto adicionam um alias nomeado para o payload:
|
||||
|
||||
```python
|
||||
class ExecutionStartContext(InterceptionContext):
|
||||
inputs: dict # Same dict as payload
|
||||
|
||||
class InputContext(InterceptionContext):
|
||||
inputs: dict # Same dict as payload
|
||||
|
||||
class OutputContext(InterceptionContext):
|
||||
output: Any # The output object
|
||||
|
||||
class ExecutionEndContext(InterceptionContext):
|
||||
output: Any # The output object (None when status == "failed")
|
||||
status: str # "completed" or "failed"
|
||||
error: BaseException | None # The exception when status == "failed"
|
||||
```
|
||||
|
||||
<Note>
|
||||
`ctx.inputs` é um alias para o dict de entradas **original**, então edições in
|
||||
place por qualquer um dos nomes são equivalentes. Se um hook anterior
|
||||
*substituiu* o payload retornando um novo dict, apenas `ctx.payload` é
|
||||
reassociado — sempre leia e escreva `ctx.payload` quando hooks puderem
|
||||
encadear.
|
||||
</Note>
|
||||
|
||||
## Execuções de Crew vs. Execuções de Flow
|
||||
|
||||
Hooks de fronteira disparam em ambos os runtimes, e a execução de uma crew
|
||||
roda internamente sobre um runtime de flow. Durante um `crew.kickoff()`, um
|
||||
hook de fronteira global portanto dispara para a fronteira da crew
|
||||
(`ctx.crew` definido, `ctx.flow` `None`) **e** para o flow interno
|
||||
(`ctx.flow` definido, `ctx.crew` `None`). Discrimine pelo runtime:
|
||||
|
||||
```python
|
||||
@on(InterceptionPoint.OUTPUT)
|
||||
def crew_output_only(ctx):
|
||||
if ctx.crew is None:
|
||||
return None # Skip the internal flow (or a bare flow)
|
||||
ctx.payload.raw = ctx.payload.raw.strip()
|
||||
```
|
||||
|
||||
## Casos de Uso Comuns
|
||||
|
||||
### Verificação de Política no Início
|
||||
|
||||
```python
|
||||
@on(InterceptionPoint.EXECUTION_START)
|
||||
def enforce_policy(ctx):
|
||||
if ctx.crew is not None and not ctx.payload.get("authorized"):
|
||||
raise HookAborted(reason="unauthorized execution", source="access-control")
|
||||
```
|
||||
|
||||
### Reescrita de Entradas
|
||||
|
||||
```python
|
||||
@on(InterceptionPoint.INPUT)
|
||||
def add_defaults(ctx):
|
||||
if ctx.crew is None:
|
||||
return None
|
||||
ctx.payload.setdefault("locale", "en-US")
|
||||
ctx.payload["topic"] = ctx.payload["topic"].strip().lower()
|
||||
```
|
||||
|
||||
Entradas reescritas fluem para a interpolação de tasks, então a execução se
|
||||
comporta como se tivesse sido iniciada com o dict modificado.
|
||||
|
||||
Prefira `INPUT` para reescrita e trate `EXECUTION_START` como o gate de
|
||||
allow/deny. Reescritas em `EXECUTION_START` continuam sendo honradas — em
|
||||
crews elas também alimentam os callbacks de `before_kickoff`; em flows elas
|
||||
se aplicam exatamente como uma reescrita de `INPUT`.
|
||||
|
||||
### Sanitização de Saída
|
||||
|
||||
```python
|
||||
import re
|
||||
|
||||
@on(InterceptionPoint.OUTPUT)
|
||||
def redact_emails(ctx):
|
||||
if ctx.crew is None:
|
||||
return None
|
||||
ctx.payload.raw = re.sub(
|
||||
r"\b[\w.+-]+@[\w-]+\.[\w.]+\b", "[EMAIL-REDACTED]", ctx.payload.raw
|
||||
)
|
||||
```
|
||||
|
||||
`OUTPUT` roda antes de `EXECUTION_END`, e ambos veem o payload (possivelmente
|
||||
substituído) de hooks anteriores; o valor final reescrito é o que `kickoff()`
|
||||
retorna.
|
||||
|
||||
### Observando Falhas
|
||||
|
||||
`EXECUTION_END` dispara exatamente uma vez por execução, tanto em sucesso
|
||||
quanto em falha. Quando a execução lança uma exceção — um erro de task, uma
|
||||
exceção de método de flow ou um `HookAborted` de um ponto anterior — o hook
|
||||
recebe `status="failed"` com a exceção em `ctx.error`, e a exceção original
|
||||
ainda propaga para fora do `kickoff()` sem alterações:
|
||||
|
||||
```python
|
||||
@on(InterceptionPoint.EXECUTION_END)
|
||||
def report_outcome(ctx):
|
||||
if ctx.status == "failed":
|
||||
notify_policy_engine(status="failed", error=repr(ctx.error))
|
||||
else:
|
||||
notify_policy_engine(status="completed")
|
||||
```
|
||||
|
||||
Duas ressalvas: `EXECUTION_END` não dispara quando `EXECUTION_START` nunca foi
|
||||
despachado (um abort no início significa que a fronteira nunca abriu, então
|
||||
não há fim para parear), e lançar `HookAborted` de um dispatch de
|
||||
`EXECUTION_END` no caminho de falha é ignorado — não resta nada para abortar,
|
||||
e o erro original prevalece.
|
||||
|
||||
## Ordenação
|
||||
|
||||
Para uma execução de crew, a ordem de fronteira é:
|
||||
|
||||
```
|
||||
EXECUTION_START → before_kickoff callbacks → INPUT → tasks execute → OUTPUT → EXECUTION_END
|
||||
```
|
||||
|
||||
Para uma execução de flow, os hooks de fronteira resolvem as entradas antes
|
||||
de os eventos de ciclo de vida começarem:
|
||||
|
||||
```
|
||||
EXECUTION_START → INPUT → FlowStartedEvent → flow methods execute → OUTPUT → EXECUTION_END → FlowFinishedEvent
|
||||
```
|
||||
|
||||
`FlowStartedEvent` carrega as entradas resolvidas pelos hooks, e reescrever
|
||||
`inputs["id"]` em um hook de fronteira redireciona a restauração de estado.
|
||||
Um abort em `EXECUTION_START` ainda aparece como `FlowStartedEvent` seguido
|
||||
de `FlowFailedEvent`, emitidos no momento do abort com o payload como
|
||||
resolvido pelos hooks que rodaram antes dele.
|
||||
|
||||
Hooks no mesmo ponto rodam em ordem de registro, hooks globais primeiro,
|
||||
depois hooks com escopo de crew. A telemetria (`HookDispatchedEvent`) é
|
||||
emitida por dispatch.
|
||||
|
||||
## Gerenciando Hooks em Testes
|
||||
|
||||
```python
|
||||
from crewai.hooks import clear_all_hooks
|
||||
|
||||
clear_all_hooks() # Clears every point, including boundaries
|
||||
```
|
||||
|
||||
## Documentação Relacionada
|
||||
|
||||
- [Visão Geral dos Hooks de Execução →](/edge/pt-BR/learn/execution-hooks)
|
||||
- [Hooks de Chamada LLM →](/edge/pt-BR/learn/llm-hooks)
|
||||
- [Hooks de Chamada de Ferramenta →](/edge/pt-BR/learn/tool-hooks)
|
||||
Reference in New Issue
Block a user