Flows에서의 인간 피드백
Flows에서의 인간 피드백 (Human Feedback in Flows)
@human_feedback 데코레이터를 사용하면 CrewAI Flows 안에서 바로 인간-인-더-루프(HITL, Human-in-the-Loop) 워크플로우를 만들 수 있어요. flow 실행을 일시 중지하고, 출력을 인간에게 보여주고, 피드백을 수집하고, 결과에 따라 다른 listener로 라우팅할 수 있어요.
출처: 문서
본문
Overview (개요)
@human_feedback 데코레이터는 CrewAI 1.8.0 이상이 필요해요. 이 기능을 사용하기 전에 설치를 업데이트했는지 확인하세요.
@human_feedback 데코레이터는 CrewAI Flows 안에서 직접 인간-인-더-루프(HITL) 워크플로우를 가능하게 해요. flow 실행을 일시 중지하고, 출력을 인간이 검토하도록 보여주고, 피드백을 수집하고, 선택적으로 피드백 결과에 따라 다른 listener로 라우팅할 수 있게 해 줘요.
특히 다음에 유용해요:
- 품질 보증(Quality assurance) — AI 생성 콘텐츠를 다운스트림에서 사용하기 전에 검토
- 의사결정 게이트(Decision gates) — 자동화된 워크플로우에서 인간이 중요한 결정을 내리게 함
- 승인 워크플로우(Approval workflows) — approve/reject/revise 패턴 구현
- 대화형 개선(Interactive refinement) — 출력을 반복적으로 개선하기 위해 피드백 수집
Quick Start (빠른 시작)
flow에 인간 피드백을 추가하는 가장 간단한 방법은 이래요:
from crewai.flow.flow import Flow, start, listen
from crewai.flow.human_feedback import human_feedback
class SimpleReviewFlow(Flow):
@start()
@human_feedback(message="Please review this content:")
def generate_content(self):
return "This is AI-generated content that needs review."
@listen(generate_content)
def process_feedback(self, result):
print(f"Content: {result.output}")
print(f"Human said: {result.feedback}")
flow = SimpleReviewFlow()
flow.kickoff()
이 flow가 실행되면:
generate_content를 실행하고 문자열을 반환해요.- 출력을 요청 메시지와 함께 사용자에게 표시해요.
- 사용자가 피드백을 입력할 때까지 기다려요 (또는 Enter를 눌러 건너뛰기).
HumanFeedbackResult객체를process_feedback에 전달해요.
The @human_feedback Decorator
Parameters (파라미터)
| Parameter | Type | Required | Description |
|---|---|---|---|
message |
str |
Yes | 인간에게 메서드 출력과 함께 보여지는 메시지 |
emit |
Sequence[str] |
No | 가능한 결과 목록. 피드백이 이 중 하나로 축약되며 @listen 데코레이터를 트리거 |
llm |
str | BaseLLM |
emit 지정 시 | 피드백을 해석해 결과로 매핑하는 LLM |
default_outcome |
str |
No | 피드백이 없을 때 사용할 결과. 반드시 emit에 있어야 함 |
metadata |
dict |
No | 엔터프라이즈 통합을 위한 추가 데이터 |
provider |
HumanFeedbackProvider |
No | 비동기/비차단 피드백용 커스텀 프로바이더. Async Human Feedback 참조 |
learn |
bool |
No | HITL 학습 활성화: 피드백에서 교훈을 추출해 향후 출력을 사전 검토. 기본값 False |
learn_limit |
int |
No | 사전 검토에 회상할 과거 교훈 최대 수. 기본값 5 |
Basic Usage (No Routing)
emit을 지정하지 않으면 데코레이터는 단순히 피드백을 수집해 다음 listener에 HumanFeedbackResult를 전달해요:
@start()
@human_feedback(message="What do you think of this analysis?")
def analyze_data(self):
return "Analysis results: Revenue up 15%, costs down 8%"
@listen(analyze_data)
def handle_feedback(self, result):
# result is a HumanFeedbackResult
print(f"Analysis: {result.output}")
print(f"Feedback: {result.feedback}")
Routing with emit
emit을 지정하면 데코레이터가 라우터가 돼요. 인간의 자유 형식 피드백을 LLM이 해석해서 지정된 결과 중 하나로 축약해요:
from crewai.flow.flow import Flow, start, listen, or_
from crewai.flow.human_feedback import human_feedback
class ReviewFlow(Flow):
@start()
def generate_content(self):
return "Draft blog post content here..."
@human_feedback(
message="Do you approve this content for publication?",
emit=["approved", "rejected", "needs_revision"],
llm="gpt-4o-mini",
default_outcome="needs_revision",
)
@listen(or_("generate_content", "needs_revision"))
def review_content(self):
return "Draft blog post content here..."
@listen("approved")
def publish(self, result):
print(f"Publishing! User said: {result.feedback}")
@listen("rejected")
def discard(self, result):
print(f"Discarding. Reason: {result.feedback}")
인간이 "needs more detail" 같은 말을 하면 LLM이 그걸 "needs_revision"으로 축약하고, or_()를 통해 review_content를 다시 트리거해요 — 즉 수정 루프(revision loop)를 만드는 거예요. 결과가 "approved" 또는 "rejected"가 될 때까지 루프가 계속돼요.
LLM은 가능할 때 구조화된 출력(function calling)을 사용해 응답이 지정한 결과 중 하나임을 보장해요. 덕분에 라우팅이 신뢰할 수 있고 예측 가능해요.
@start() 메서드는 flow 시작 시 한 번만 실행돼요. 수정 루프가 필요하다면 start 메서드와 review 메서드를 분리하고, review 메서드에 @listen(or_("trigger", "revision_outcome"))를 사용해 셀프 루프를 활성화하세요.
HumanFeedbackResult
HumanFeedbackResult dataclass는 인간 피드백 상호작용에 대한 모든 정보를 담아요:
from crewai.flow.human_feedback import HumanFeedbackResult
@dataclass
class HumanFeedbackResult:
output: Any # The original method output shown to the human
feedback: str # The raw feedback text from the human
outcome: str | None # The collapsed outcome (if emit was specified)
timestamp: datetime # When the feedback was received
method_name: str # Name of the decorated method
metadata: dict # Any metadata passed to the decorator
Accessing in Listeners
emit이 있는 @human_feedback 메서드에 의해 listener가 트리거되면 HumanFeedbackResult를 받아요:
@listen("approved")
def on_approval(self, result: HumanFeedbackResult):
print(f"Original output: {result.output}")
print(f"User feedback: {result.feedback}")
print(f"Outcome: {result.outcome}") # "approved"
print(f"Received at: {result.timestamp}")
Accessing Feedback History (피드백 기록 접근)
Flow 클래스는 인간 피드백에 접근하는 두 가지 속성을 제공해요.
last_human_feedback
가장 최근의 HumanFeedbackResult를 반환해요:
@listen(some_method)
def check_feedback(self):
if self.last_human_feedback:
print(f"Last feedback: {self.last_human_feedback.feedback}")
human_feedback_history
flow 중 수집된 모든 HumanFeedbackResult 객체의 목록이에요:
@listen(final_step)
def summarize(self):
print(f"Total feedback collected: {len(self.human_feedback_history)}")
for i, fb in enumerate(self.human_feedback_history):
print(f"{i+1}. {fb.method_name}: {fb.outcome or 'no routing'}")
각 HumanFeedbackResult는 human_feedback_history에 추가되므로, 여러 피드백 단계가 서로 덮어쓰지 않아요. 이 목록을 사용해 flow 중 수집된 모든 피드백에 접근하세요.
Complete Example: Content Approval Workflow (전체 예시: 콘텐츠 승인 워크플로우)
수정 루프가 있는 콘텐츠 검토·승인 워크플로우의 전체 예시예요:
from crewai.flow.flow import Flow, start, listen, or_
from crewai.flow.human_feedback import human_feedback, HumanFeedbackResult
from pydantic import BaseModel
class ContentState(BaseModel):
draft: str = ""
revision_count: int = 0
status: str = "pending"
class ContentApprovalFlow(Flow[ContentState]):
"""A flow that generates content and loops until the human approves."""
@start()
def generate_draft(self):
self.state.draft = "# AI Safety\n\nThis is a draft about AI Safety..."
return self.state.draft
@human_feedback(
message="Please review this draft. Approve, reject, or describe what needs changing:",
emit=["approved", "rejected", "needs_revision"],
llm="gpt-4o-mini",
default_outcome="needs_revision",
)
@listen(or_("generate_draft", "needs_revision"))
def review_draft(self):
self.state.revision_count += 1
return f"{self.state.draft} (v{self.state.revision_count})"
@listen("approved")
def publish_content(self, result: HumanFeedbackResult):
self.state.status = "published"
print(f"Content approved and published! Reviewer said: {result.feedback}")
return "published"
@listen("rejected")
def handle_rejection(self, result: HumanFeedbackResult):
self.state.status = "rejected"
print(f"Content rejected. Reason: {result.feedback}")
return "rejected"
flow = ContentApprovalFlow()
result = flow.kickoff()
print(f"\nFlow completed. Status: {flow.state.status}, Reviews: {flow.state.revision_count}")
실행 출력 예시:
==================================================
OUTPUT FOR REVIEW:
==================================================
# AI Safety
This is a draft about AI Safety... (v1)
==================================================
Please review this draft. Approve, reject, or describe what needs changing:
(Press Enter to skip, or type your feedback)
Your feedback: Needs more detail on alignment research
==================================================
OUTPUT FOR REVIEW:
==================================================
# AI Safety
This is a draft about AI Safety... (v2)
==================================================
Please review this draft. Approve, reject, or describe what needs changing:
(Press Enter to skip, or type your feedback)
Your feedback: Looks good, approved!
Content approved and published! Reviewer said: Looks good, approved!
Flow completed. Status: published, Reviews: 2
핵심 패턴은 @listen(or_("generate_draft", "needs_revision"))이에요 — review 메서드가 초기 트리거와 자신의 수정 결과를 모두 듣기 때문에, 인간이 승인하거나 거부할 때까지 반복되는 셀프 루프가 생겨요.
Combining with Other Decorators (다른 데코레이터와 조합)
@human_feedback 데코레이터는 @start(), @listen(), or_()와 함께 동작해요. 두 데코레이터 순서 모두 동작하지만(프레임워크가 양방향으로 속성을 전파), 권장 패턴은 이래요:
# One-shot review at the start of a flow (no self-loop)
@start()
@human_feedback(message="Review this:", emit=["approved", "rejected"], llm="gpt-4o-mini")
def my_start_method(self):
return "content"
# Linear review on a listener (no self-loop)
@listen(other_method)
@human_feedback(message="Review this too:", emit=["good", "bad"], llm="gpt-4o-mini")
def my_listener(self, data):
return f"processed: {data}"
# Self-loop: review that can loop back for revisions
@human_feedback(message="Approve or revise?", emit=["approved", "revise"], llm="gpt-4o-mini")
@listen(or_("upstream_method", "revise"))
def review_with_loop(self):
return "content for review"
Self-loop pattern (셀프 루프 패턴)
수정 루프를 만들려면 review 메서드가 or_()를 사용해 상위 트리거와 자신의 수정 결과 둘 다 들어야 해요:
@start()
def generate(self):
return "initial draft"
@human_feedback(
message="Approve or request changes?",
emit=["revise", "approved"],
llm="gpt-4o-mini",
default_outcome="approved",
)
@listen(or_("generate", "revise"))
def review(self):
return "content"
@listen("approved")
def publish(self):
return "published"
결과가 "revise"면 flow가 review로 다시 라우팅돼요 (or_()를 통해 "revise"를 듣기 때문). 결과가 "approved"면 flow가 publish로 계속 진행돼요. flow 엔진이 라우터를 "한 번만 실행(unfire-once)" 규칙에서 면제해 주므로, 각 루프 반복마다 다시 실행될 수 있거든요.
Chained routers (연결된 라우터)
한 라우터의 결과에 의해 트리거된 listener가 자신도 라우터가 될 수 있어요:
@start()
def generate(self):
return "draft content"
@human_feedback(message="First review:", emit=["approved", "rejected"], llm="gpt-4o-mini")
@listen("generate")
def first_review(self):
return "draft content"
@human_feedback(message="Final review:", emit=["publish", "hold"], llm="gpt-4o-mini")
@listen("approved")
def final_review(self, prev):
return "final content"
@listen("publish")
def on_publish(self, prev):
return "published"
@listen("hold")
def on_hold(self, prev):
return "held for later"
Limitations (제한 사항)
@start()메서드는 한 번만 실행돼요 :@start()메서드는 셀프 루프할 수 없어요. 수정 주기가 필요하다면 별도의@start()메서드를 진입점으로 사용하고@human_feedback을@listen()메서드에 두세요.- 같은 메서드에
@start()+@listen()불가 : Flow 프레임워크 제약이에요. 메서드는 시작 지점이거나 listener이거나 둘 중 하나예요.
Best Practices (모범 사례)
1. 명확한 요청 메시지 작성
message 파라미터는 인간이 보는 것이에요. 실행 가능하게 만드세요:
# ✅ Good - clear and actionable
@human_feedback(message="Does this summary accurately capture the key points? Reply 'yes' or explain what's missing:")
# ❌ Bad - vague
@human_feedback(message="Review this:")
2. 의미 있는 결과 선택
emit을 사용할 때는 인간의 응답에 자연스럽게 매핑되는 결과를 고르세요:
# ✅ Good - natural language outcomes
emit=["approved", "rejected", "needs_more_detail"]
# ❌ Bad - technical or unclear
emit=["state_1", "state_2", "state_3"]
3. 항상 기본 결과 제공
사용자가 입력 없이 Enter를 누르는 경우를 처리하려면 default_outcome을 사용하세요:
@human_feedback(
message="Approve? (press Enter to request revision)",
emit=["approved", "needs_revision"],
llm="gpt-4o-mini",
default_outcome="needs_revision", # Safe default
)
4. 감사 추적을 위해 피드백 기록 사용
human_feedback_history에 접근해 감사 로그를 만들어요:
@listen(final_step)
def create_audit_log(self):
log = []
for fb in self.human_feedback_history:
log.append({
"step": fb.method_name,
"outcome": fb.outcome,
"feedback": fb.feedback,
"timestamp": fb.timestamp.isoformat(),
})
return log
5. 라우팅 여부에 따른 피드백 처리
워크플로우를 설계할 때 라우팅이 필요한지 고려하세요:
| Scenario | Use |
|---|---|
| 피드백 텍스트만 필요한 간단한 검토 | No emit |
| 응답에 따라 다른 경로로 분기해야 할 때 | Use emit |
| approve/reject/revise 승인 게이트 | Use emit |
| 로깅용 의견 수집만 | No emit |
Async Human Feedback (비차단)
기본적으로 @human_feedback은 콘솔 입력을 기다리며 실행을 차단해요. 프로덕션 애플리케이션에서는 Slack, 이메일, 웹훅, API 같은 외부 시스템과 통합되는 async/non-blocking 피드백이 필요할 수 있어요.
The Provider Abstraction
provider 파라미터로 커스텀 피드백 수집 전략을 지정하세요:
from crewai.flow import Flow, start, human_feedback, HumanFeedbackProvider, HumanFeedbackPending, PendingFeedbackContext
class WebhookProvider(HumanFeedbackProvider):
"""Provider that pauses flow and waits for webhook callback."""
def __init__(self, webhook_url: str):
self.webhook_url = webhook_url
def request_feedback(self, context: PendingFeedbackContext, flow: Flow) -> str:
# Notify external system (e.g., send Slack message, create ticket)
self.send_notification(context)
# Pause execution - framework handles persistence automatically
raise HumanFeedbackPending(
context=context,
callback_info={"webhook_url": f"{self.webhook_url}/{context.flow_id}"}
)
class ReviewFlow(Flow):
@start()
@human_feedback(
message="Review this content:",
emit=["approved", "rejected"],
llm="gpt-4o-mini",
provider=WebhookProvider("https://myapp.com/api"),
)
def generate_content(self):
return "AI-generated content..."
@listen("approved")
def publish(self, result):
return "Published!"
flow 프레임워크는 HumanFeedbackPending이 raise될 때 상태를 자동으로 영속화해요. 여러분의 프로바이더는 외부 시스템에 알리고 예외를 raise하기만 하면 돼요 — 수동 영속화 호출은 필요 없어요.
Handling Paused Flows (일시 중지된 flow 처리)
async 프로바이더를 사용할 때 kickoff()는 예외를 raise하는 대신 HumanFeedbackPending 객체를 반환해요:
flow = ReviewFlow()
result = flow.kickoff()
if isinstance(result, HumanFeedbackPending):
# Flow is paused, state is automatically persisted
print(f"Waiting for feedback at: {result.callback_info['webhook_url']}")
print(f"Flow ID: {result.context.flow_id}")
else:
# Normal completion
print(f"Flow completed: {result}")
Resuming a Paused Flow (일시 중지된 flow 재개)
피드백이 도착하면(예: 웹훅으로) flow를 재개해요:
# Sync handler:
def handle_feedback_webhook(flow_id: str, feedback: str):
flow = ReviewFlow.from_pending(flow_id)
result = flow.resume(feedback)
return result
# Async handler (FastAPI, aiohttp, etc.):
async def handle_feedback_webhook(flow_id: str, feedback: str):
flow = ReviewFlow.from_pending(flow_id)
result = await flow.resume_async(feedback)
return result
Key Types (핵심 타입)
| Type | Description |
|---|---|
HumanFeedbackProvider |
커스텀 피드백 프로바이더를 위한 프로토콜 |
PendingFeedbackContext |
일시 중지된 flow 재개에 필요한 모든 정보 |
HumanFeedbackPending |
피드백을 위해 flow가 일시 중지됐을 때 kickoff()가 반환 |
ConsoleProvider |
기본 차단 콘솔 입력 프로바이더 |
PendingFeedbackContext
컨텍스트는 재개에 필요한 모든 것을 담아요:
@dataclass
class PendingFeedbackContext:
flow_id: str # Unique identifier for this flow execution
flow_class: str # Fully qualified class name
method_name: str # Method that triggered feedback
method_output: Any # Output shown to the human
message: str # The request message
emit: list[str] | None # Possible outcomes for routing
default_outcome: str | None
metadata: dict # Custom metadata
llm: str | None # LLM for outcome collapsing
requested_at: datetime
Complete Async Flow Example
from crewai.flow import (
Flow, start, listen, human_feedback,
HumanFeedbackProvider, HumanFeedbackPending, PendingFeedbackContext
)
class SlackNotificationProvider(HumanFeedbackProvider):
"""Provider that sends Slack notifications and pauses for async feedback."""
def __init__(self, channel: str):
self.channel = channel
def request_feedback(self, context: PendingFeedbackContext, flow: Flow) -> str:
# Send Slack notification (implement your own)
slack_thread_id = self.post_to_slack(
channel=self.channel,
message=f"Review needed:\n\n{context.method_output}\n\n{context.message}",
)
# Pause execution - framework handles persistence automatically
raise HumanFeedbackPending(
context=context,
callback_info={
"slack_channel": self.channel,
"thread_id": slack_thread_id,
}
)
class ContentPipeline(Flow):
@start()
@human_feedback(
message="Approve this content for publication?",
emit=["approved", "rejected"],
llm="gpt-4o-mini",
default_outcome="rejected",
provider=SlackNotificationProvider("#content-reviews"),
)
def generate_content(self):
return "AI-generated blog post content..."
@listen("approved")
def publish(self, result):
print(f"Publishing! Reviewer said: {result.feedback}")
return {"status": "published"}
@listen("rejected")
def archive(self, result):
print(f"Archived. Reason: {result.feedback}")
return {"status": "archived"}
# Starting the flow (will pause and wait for Slack response)
def start_content_pipeline():
flow = ContentPipeline()
result = flow.kickoff()
if isinstance(result, HumanFeedbackPending):
return {"status": "pending", "flow_id": result.context.flow_id}
return result
# Resuming when Slack webhook fires (sync handler)
def on_slack_feedback(flow_id: str, slack_message: str):
flow = ContentPipeline.from_pending(flow_id)
result = flow.resume(slack_message)
return result
# If your handler is async (FastAPI, aiohttp, Slack Bolt async, etc.)
async def on_slack_feedback_async(flow_id: str, slack_message: str):
flow = ContentPipeline.from_pending(flow_id)
result = await flow.resume_async(slack_message)
return result
async 웹 프레임워크(FastAPI, aiohttp, Slack Bolt async mode)를 사용한다면 flow.resume() 대신 await flow.resume_async()를 사용하세요. 실행 중인 이벤트 루프 안에서 resume()을 호출하면 RuntimeError가 raise돼요.
Async 피드백 모범 사례
- 반환 타입 확인 — 일시 중지됐을 때
kickoff()는HumanFeedbackPending을 반환해요 — try/except가 필요 없어요. - 올바른 resume 메서드 사용 — sync 코드에서는
resume()을, async 코드에서는await resume_async()를 사용하세요. - callback 정보 저장 — 웹훅 URL, 티켓 ID 등을 저장하려면
callback_info를 사용하세요. - 멱등성 구현 — 안전을 위해 resume 핸들러는 멱등(idempotent)이어야 해요.
- 자동 영속화 —
HumanFeedbackPending이 raise되면 상태가 자동으로 저장되며 기본적으로SQLiteFlowPersistence를 사용해요. - 커스텀 영속화 — 필요하다면
from_pending()에 커스텀 영속화 인스턴스를 전달하세요.
Learning from Feedback (피드백에서 학습하기)
learn=True 파라미터는 인간 리뷰어와 메모리 시스템 사이의 피드백 루프를 활성화해요. 활성화하면 시스템이 과거 인간 수정 사항에서 학습해 출력을 점진적으로 개선해요.
How It Works (동작 원리)
- 피드백 후 — LLM이 출력 + 피드백에서 일반화 가능한 교훈을 추출해
source="hitl"로 메모리에 저장해요. 피드백이 단순 승인(예: "looks good")이라면 아무것도 저장하지 않아요. - 다음 검토 전 — 과거 HITL 교훈을 메모리에서 회상해 LLM이 인간이 보기 전에 출력을 개선하는 데 적용해요.
시간이 지나면서, 각 수정이 향후 검토를 알려주기 때문에 인간이 보는 사전 검토된 출력이 점점 더 좋아져요.
Example
class ArticleReviewFlow(Flow):
@start()
def generate_article(self):
return self.crew.kickoff(inputs={"topic": "AI Safety"}).raw
@human_feedback(
message="Review this article draft:",
emit=["approved", "needs_revision"],
llm="gpt-4o-mini",
learn=True, # enable HITL learning
)
@listen(or_("generate_article", "needs_revision"))
def review_article(self):
return self.last_human_feedback.output if self.last_human_feedback else "article draft"
@listen("approved")
def publish(self):
print(f"Publishing: {self.last_human_feedback.output}")
첫 실행 — 인간이 원본 출력을 보고 "Always include citations for factual claims."라고 말해요. 이 교훈이 추출되어 메모리에 저장돼요. 두 번째 실행 — 시스템이 인용 교훈을 회상해, 인용을 추가하도록 출력을 사전 검토한 뒤 개선된 버전을 보여줘요. 인간의 역할이 "모두 고치기"에서 "시스템이 놓친 것을 잡기"로 바뀌어요.
Configuration (구성)
| Parameter | Default | Description |
|---|---|---|
learn |
False |
HITL 학습 활성화 |
learn_limit |
5 |
사전 검토에 회상할 과거 교훈 최대 수 |
Key Design Decisions (핵심 설계 결정)
- 모든 것에 같은 LLM 사용 — 데코레이터의
llm파라미터가 결과 축약, 교훈 증류, 사전 검토에 모두 공유돼요. 여러 모델을 구성할 필요가 없어요. - 구조화된 출력 — 증류와 사전 검토 모두 LLM이 지원할 때 Pydantic 모델로 function calling을 사용하고, 그렇지 않으면 텍스트 파싱으로 폴백해요.
- 비차단 저장 — 교훈은 백그라운드 스레드에서 실행되는
remember_many()를 통해 저장돼요 — flow가 즉시 계속돼요. - 우아한 저하(Graceful degradation) — 증류 중 LLM이 실패하면 저장하지 않아요. 사전 검토 중 실패하면 원본 출력을 보여줘요. 어느 실패도 flow를 막지 않아요.
- 스코프/카테고리 불필요 — 교훈을 저장할 때
source만 전달돼요. 인코딩 파이프라인이 스코프, 카테고리, 중요도를 자동으로 추론해요.
learn=True는 Flow가 메모리를 사용할 수 있어야 해요. Flow는 기본적으로 자동으로 메모리를 얻지만, _skip_auto_memory로 비활성화했다면 HITL 학습은 조용히 건너뛰어져요.
Related Documentation (관련 문서)
- Flows Overview — CrewAI Flows에 대해 배우기
- Flow State Management — flow에서 상태 관리하기
- Flow Persistence — flow 상태 영속화
- Routing with @router — 조건부 라우팅에 대한 더 많은 내용
- Human Input on Execution — 태스크 레벨 인간 입력
- Memory — HITL 학습이 사용하는 통합 메모리 시스템