mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-08 15:48:29 +00:00
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
* feat: introduce human feedback events and decorator for flow methods - Added HumanFeedbackRequestedEvent and HumanFeedbackReceivedEvent classes to handle human feedback interactions within flows. - Implemented the @human_feedback decorator to facilitate human-in-the-loop workflows, allowing for feedback collection and routing based on responses. - Enhanced Flow class to store human feedback history and manage feedback outcomes. - Updated flow wrappers to preserve attributes from methods decorated with @human_feedback. - Added integration and unit tests for the new human feedback functionality, ensuring proper validation and routing behavior. * adding deployment docs * New docs * fix printer * wrong change * Adding Async Support feat: enhance human feedback support in flows - Updated the @human_feedback decorator to use 'message' parameter instead of 'request' for clarity. - Introduced new FlowPausedEvent and MethodExecutionPausedEvent to handle flow and method pauses during human feedback. - Added ConsoleProvider for synchronous feedback collection and integrated async feedback capabilities. - Implemented SQLite persistence for managing pending feedback context. - Expanded documentation to include examples of async human feedback usage and best practices. * linter * fix * migrating off printer * updating docs * new tests * doc update
582 lines
20 KiB
Plaintext
582 lines
20 KiB
Plaintext
---
|
|
title: Feedback Humano em Flows
|
|
description: Aprenda como integrar feedback humano diretamente nos seus CrewAI Flows usando o decorador @human_feedback
|
|
icon: user-check
|
|
mode: "wide"
|
|
---
|
|
|
|
## Visão Geral
|
|
|
|
O decorador `@human_feedback` permite fluxos de trabalho human-in-the-loop (HITL) diretamente nos CrewAI Flows. Ele permite pausar a execução do flow, apresentar a saída para um humano revisar, coletar seu feedback e, opcionalmente, rotear para diferentes listeners com base no resultado do feedback.
|
|
|
|
Isso é particularmente valioso para:
|
|
|
|
- **Garantia de qualidade**: Revisar conteúdo gerado por IA antes de ser usado downstream
|
|
- **Portões de decisão**: Deixar humanos tomarem decisões críticas em fluxos automatizados
|
|
- **Fluxos de aprovação**: Implementar padrões de aprovar/rejeitar/revisar
|
|
- **Refinamento interativo**: Coletar feedback para melhorar saídas iterativamente
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
A[Método do Flow] --> B[Saída Gerada]
|
|
B --> C[Humano Revisa]
|
|
C --> D{Feedback}
|
|
D -->|emit especificado| E[LLM Mapeia para Outcome]
|
|
D -->|sem emit| F[HumanFeedbackResult]
|
|
E --> G["@listen('approved')"]
|
|
E --> H["@listen('rejected')"]
|
|
F --> I[Próximo Listener]
|
|
```
|
|
|
|
## Início Rápido
|
|
|
|
Aqui está a maneira mais simples de adicionar feedback humano a um flow:
|
|
|
|
```python Code
|
|
from crewai.flow.flow import Flow, start, listen
|
|
from crewai.flow.human_feedback import human_feedback
|
|
|
|
class SimpleReviewFlow(Flow):
|
|
@start()
|
|
@human_feedback(message="Por favor, revise este conteúdo:")
|
|
def generate_content(self):
|
|
return "Este é um conteúdo gerado por IA que precisa de revisão."
|
|
|
|
@listen(generate_content)
|
|
def process_feedback(self, result):
|
|
print(f"Conteúdo: {result.output}")
|
|
print(f"Humano disse: {result.feedback}")
|
|
|
|
flow = SimpleReviewFlow()
|
|
flow.kickoff()
|
|
```
|
|
|
|
Quando este flow é executado, ele irá:
|
|
1. Executar `generate_content` e retornar a string
|
|
2. Exibir a saída para o usuário com a mensagem de solicitação
|
|
3. Aguardar o usuário digitar o feedback (ou pressionar Enter para pular)
|
|
4. Passar um objeto `HumanFeedbackResult` para `process_feedback`
|
|
|
|
## O Decorador @human_feedback
|
|
|
|
### Parâmetros
|
|
|
|
| Parâmetro | Tipo | Obrigatório | Descrição |
|
|
|-----------|------|-------------|-----------|
|
|
| `message` | `str` | Sim | A mensagem mostrada ao humano junto com a saída do método |
|
|
| `emit` | `Sequence[str]` | Não | Lista de possíveis outcomes. O feedback é mapeado para um destes, que dispara decoradores `@listen` |
|
|
| `llm` | `str \| BaseLLM` | Quando `emit` especificado | LLM usado para interpretar o feedback e mapear para um outcome |
|
|
| `default_outcome` | `str` | Não | Outcome a usar se nenhum feedback for fornecido. Deve estar em `emit` |
|
|
| `metadata` | `dict` | Não | Dados adicionais para integrações enterprise |
|
|
| `provider` | `HumanFeedbackProvider` | Não | Provider customizado para feedback assíncrono/não-bloqueante. Veja [Feedback Humano Assíncrono](#feedback-humano-assíncrono-não-bloqueante) |
|
|
|
|
### Uso Básico (Sem Roteamento)
|
|
|
|
Quando você não especifica `emit`, o decorador simplesmente coleta o feedback e passa um `HumanFeedbackResult` para o próximo listener:
|
|
|
|
```python Code
|
|
@start()
|
|
@human_feedback(message="O que você acha desta análise?")
|
|
def analyze_data(self):
|
|
return "Resultados da análise: Receita aumentou 15%, custos diminuíram 8%"
|
|
|
|
@listen(analyze_data)
|
|
def handle_feedback(self, result):
|
|
# result é um HumanFeedbackResult
|
|
print(f"Análise: {result.output}")
|
|
print(f"Feedback: {result.feedback}")
|
|
```
|
|
|
|
### Roteamento com emit
|
|
|
|
Quando você especifica `emit`, o decorador se torna um roteador. O feedback livre do humano é interpretado por um LLM e mapeado para um dos outcomes especificados:
|
|
|
|
```python Code
|
|
@start()
|
|
@human_feedback(
|
|
message="Você aprova este conteúdo para publicação?",
|
|
emit=["approved", "rejected", "needs_revision"],
|
|
llm="gpt-4o-mini",
|
|
default_outcome="needs_revision",
|
|
)
|
|
def review_content(self):
|
|
return "Rascunho do post do blog aqui..."
|
|
|
|
@listen("approved")
|
|
def publish(self, result):
|
|
print(f"Publicando! Usuário disse: {result.feedback}")
|
|
|
|
@listen("rejected")
|
|
def discard(self, result):
|
|
print(f"Descartando. Motivo: {result.feedback}")
|
|
|
|
@listen("needs_revision")
|
|
def revise(self, result):
|
|
print(f"Revisando baseado em: {result.feedback}")
|
|
```
|
|
|
|
<Tip>
|
|
O LLM usa saídas estruturadas (function calling) quando disponível para garantir que a resposta seja um dos seus outcomes especificados. Isso torna o roteamento confiável e previsível.
|
|
</Tip>
|
|
|
|
## HumanFeedbackResult
|
|
|
|
O dataclass `HumanFeedbackResult` contém todas as informações sobre uma interação de feedback humano:
|
|
|
|
```python Code
|
|
from crewai.flow.human_feedback import HumanFeedbackResult
|
|
|
|
@dataclass
|
|
class HumanFeedbackResult:
|
|
output: Any # A saída original do método mostrada ao humano
|
|
feedback: str # O texto bruto do feedback do humano
|
|
outcome: str | None # O outcome mapeado (se emit foi especificado)
|
|
timestamp: datetime # Quando o feedback foi recebido
|
|
method_name: str # Nome do método decorado
|
|
metadata: dict # Qualquer metadata passado ao decorador
|
|
```
|
|
|
|
### Acessando em Listeners
|
|
|
|
Quando um listener é disparado por um método `@human_feedback` com `emit`, ele recebe o `HumanFeedbackResult`:
|
|
|
|
```python Code
|
|
@listen("approved")
|
|
def on_approval(self, result: HumanFeedbackResult):
|
|
print(f"Saída original: {result.output}")
|
|
print(f"Feedback do usuário: {result.feedback}")
|
|
print(f"Outcome: {result.outcome}") # "approved"
|
|
print(f"Recebido em: {result.timestamp}")
|
|
```
|
|
|
|
## Acessando o Histórico de Feedback
|
|
|
|
A classe `Flow` fornece dois atributos para acessar o feedback humano:
|
|
|
|
### last_human_feedback
|
|
|
|
Retorna o `HumanFeedbackResult` mais recente:
|
|
|
|
```python Code
|
|
@listen(some_method)
|
|
def check_feedback(self):
|
|
if self.last_human_feedback:
|
|
print(f"Último feedback: {self.last_human_feedback.feedback}")
|
|
```
|
|
|
|
### human_feedback_history
|
|
|
|
Uma lista de todos os objetos `HumanFeedbackResult` coletados durante o flow:
|
|
|
|
```python Code
|
|
@listen(final_step)
|
|
def summarize(self):
|
|
print(f"Total de feedbacks coletados: {len(self.human_feedback_history)}")
|
|
for i, fb in enumerate(self.human_feedback_history):
|
|
print(f"{i+1}. {fb.method_name}: {fb.outcome or 'sem roteamento'}")
|
|
```
|
|
|
|
<Warning>
|
|
Cada `HumanFeedbackResult` é adicionado a `human_feedback_history`, então múltiplos passos de feedback não sobrescrevem uns aos outros. Use esta lista para acessar todo o feedback coletado durante o flow.
|
|
</Warning>
|
|
|
|
## Exemplo Completo: Fluxo de Aprovação de Conteúdo
|
|
|
|
Aqui está um exemplo completo implementando um fluxo de revisão e aprovação de conteúdo:
|
|
|
|
<CodeGroup>
|
|
|
|
```python Code
|
|
from crewai.flow.flow import Flow, start, listen
|
|
from crewai.flow.human_feedback import human_feedback, HumanFeedbackResult
|
|
from pydantic import BaseModel
|
|
|
|
|
|
class ContentState(BaseModel):
|
|
topic: str = ""
|
|
draft: str = ""
|
|
final_content: str = ""
|
|
revision_count: int = 0
|
|
|
|
|
|
class ContentApprovalFlow(Flow[ContentState]):
|
|
"""Um flow que gera conteúdo e obtém aprovação humana."""
|
|
|
|
@start()
|
|
def get_topic(self):
|
|
self.state.topic = input("Sobre qual tópico devo escrever? ")
|
|
return self.state.topic
|
|
|
|
@listen(get_topic)
|
|
def generate_draft(self, topic):
|
|
# Em uso real, isso chamaria um LLM
|
|
self.state.draft = f"# {topic}\n\nEste é um rascunho sobre {topic}..."
|
|
return self.state.draft
|
|
|
|
@listen(generate_draft)
|
|
@human_feedback(
|
|
message="Por favor, revise este rascunho. Responda 'approved', 'rejected', ou forneça feedback de revisão:",
|
|
emit=["approved", "rejected", "needs_revision"],
|
|
llm="gpt-4o-mini",
|
|
default_outcome="needs_revision",
|
|
)
|
|
def review_draft(self, draft):
|
|
return draft
|
|
|
|
@listen("approved")
|
|
def publish_content(self, result: HumanFeedbackResult):
|
|
self.state.final_content = result.output
|
|
print("\n✅ Conteúdo aprovado e publicado!")
|
|
print(f"Comentário do revisor: {result.feedback}")
|
|
return "published"
|
|
|
|
@listen("rejected")
|
|
def handle_rejection(self, result: HumanFeedbackResult):
|
|
print("\n❌ Conteúdo rejeitado")
|
|
print(f"Motivo: {result.feedback}")
|
|
return "rejected"
|
|
|
|
@listen("needs_revision")
|
|
def revise_content(self, result: HumanFeedbackResult):
|
|
self.state.revision_count += 1
|
|
print(f"\n📝 Revisão #{self.state.revision_count} solicitada")
|
|
print(f"Feedback: {result.feedback}")
|
|
|
|
# Em um flow real, você pode voltar para generate_draft
|
|
# Para este exemplo, apenas reconhecemos
|
|
return "revision_requested"
|
|
|
|
|
|
# Executar o flow
|
|
flow = ContentApprovalFlow()
|
|
result = flow.kickoff()
|
|
print(f"\nFlow concluído. Revisões solicitadas: {flow.state.revision_count}")
|
|
```
|
|
|
|
```text Output
|
|
Sobre qual tópico devo escrever? Segurança em IA
|
|
|
|
==================================================
|
|
OUTPUT FOR REVIEW:
|
|
==================================================
|
|
# Segurança em IA
|
|
|
|
Este é um rascunho sobre Segurança em IA...
|
|
==================================================
|
|
|
|
Por favor, revise este rascunho. Responda 'approved', 'rejected', ou forneça feedback de revisão:
|
|
(Press Enter to skip, or type your feedback)
|
|
|
|
Your feedback: Parece bom, aprovado!
|
|
|
|
✅ Conteúdo aprovado e publicado!
|
|
Comentário do revisor: Parece bom, aprovado!
|
|
|
|
Flow concluído. Revisões solicitadas: 0
|
|
```
|
|
|
|
</CodeGroup>
|
|
|
|
## Combinando com Outros Decoradores
|
|
|
|
O decorador `@human_feedback` funciona com outros decoradores de flow. Coloque-o como o decorador mais interno (mais próximo da função):
|
|
|
|
```python Code
|
|
# Correto: @human_feedback é o mais interno (mais próximo da função)
|
|
@start()
|
|
@human_feedback(message="Revise isto:")
|
|
def my_start_method(self):
|
|
return "content"
|
|
|
|
@listen(other_method)
|
|
@human_feedback(message="Revise isto também:")
|
|
def my_listener(self, data):
|
|
return f"processed: {data}"
|
|
```
|
|
|
|
<Tip>
|
|
Coloque `@human_feedback` como o decorador mais interno (último/mais próximo da função) para que ele envolva o método diretamente e possa capturar o valor de retorno antes de passar para o sistema de flow.
|
|
</Tip>
|
|
|
|
## Melhores Práticas
|
|
|
|
### 1. Escreva Mensagens de Solicitação Claras
|
|
|
|
O parâmetro `message` é o que o humano vê. Torne-o acionável:
|
|
|
|
```python Code
|
|
# ✅ Bom - claro e acionável
|
|
@human_feedback(message="Este resumo captura com precisão os pontos-chave? Responda 'sim' ou explique o que está faltando:")
|
|
|
|
# ❌ Ruim - vago
|
|
@human_feedback(message="Revise isto:")
|
|
```
|
|
|
|
### 2. Escolha Outcomes Significativos
|
|
|
|
Ao usar `emit`, escolha outcomes que mapeiem naturalmente para respostas humanas:
|
|
|
|
```python Code
|
|
# ✅ Bom - outcomes em linguagem natural
|
|
emit=["approved", "rejected", "needs_more_detail"]
|
|
|
|
# ❌ Ruim - técnico ou pouco claro
|
|
emit=["state_1", "state_2", "state_3"]
|
|
```
|
|
|
|
### 3. Sempre Forneça um Outcome Padrão
|
|
|
|
Use `default_outcome` para lidar com casos onde usuários pressionam Enter sem digitar:
|
|
|
|
```python Code
|
|
@human_feedback(
|
|
message="Aprovar? (pressione Enter para solicitar revisão)",
|
|
emit=["approved", "needs_revision"],
|
|
llm="gpt-4o-mini",
|
|
default_outcome="needs_revision", # Padrão seguro
|
|
)
|
|
```
|
|
|
|
### 4. Use o Histórico de Feedback para Trilhas de Auditoria
|
|
|
|
Acesse `human_feedback_history` para criar logs de auditoria:
|
|
|
|
```python Code
|
|
@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. Trate Feedback Roteado e Não Roteado
|
|
|
|
Ao projetar flows, considere se você precisa de roteamento:
|
|
|
|
| Cenário | Use |
|
|
|---------|-----|
|
|
| Revisão simples, só precisa do texto do feedback | Sem `emit` |
|
|
| Precisa ramificar para caminhos diferentes baseado na resposta | Use `emit` |
|
|
| Portões de aprovação com aprovar/rejeitar/revisar | Use `emit` |
|
|
| Coletando comentários apenas para logging | Sem `emit` |
|
|
|
|
## Feedback Humano Assíncrono (Não-Bloqueante - Human in the loop)
|
|
|
|
Por padrão, `@human_feedback` bloqueia a execução aguardando entrada no console. Para aplicações de produção, você pode precisar de feedback **assíncrono/não-bloqueante** que se integre com sistemas externos como Slack, email, webhooks ou APIs.
|
|
|
|
### A Abstração de Provider
|
|
|
|
Use o parâmetro `provider` para especificar uma estratégia customizada de coleta de feedback:
|
|
|
|
```python Code
|
|
from crewai.flow import Flow, start, human_feedback, HumanFeedbackProvider, HumanFeedbackPending, PendingFeedbackContext
|
|
|
|
class WebhookProvider(HumanFeedbackProvider):
|
|
"""Provider que pausa o flow e aguarda callback de webhook."""
|
|
|
|
def __init__(self, webhook_url: str):
|
|
self.webhook_url = webhook_url
|
|
|
|
def request_feedback(self, context: PendingFeedbackContext, flow: Flow) -> str:
|
|
# Notifica sistema externo (ex: envia mensagem Slack, cria ticket)
|
|
self.send_notification(context)
|
|
|
|
# Pausa execução - framework cuida da persistência automaticamente
|
|
raise HumanFeedbackPending(
|
|
context=context,
|
|
callback_info={"webhook_url": f"{self.webhook_url}/{context.flow_id}"}
|
|
)
|
|
|
|
class ReviewFlow(Flow):
|
|
@start()
|
|
@human_feedback(
|
|
message="Revise este conteúdo:",
|
|
emit=["approved", "rejected"],
|
|
llm="gpt-4o-mini",
|
|
provider=WebhookProvider("https://myapp.com/api"),
|
|
)
|
|
def generate_content(self):
|
|
return "Conteúdo gerado por IA..."
|
|
|
|
@listen("approved")
|
|
def publish(self, result):
|
|
return "Publicado!"
|
|
```
|
|
|
|
<Tip>
|
|
O framework de flow **persiste automaticamente o estado** quando `HumanFeedbackPending` é lançado. Seu provider só precisa notificar o sistema externo e lançar a exceção—não são necessárias chamadas manuais de persistência.
|
|
</Tip>
|
|
|
|
### Tratando Flows Pausados
|
|
|
|
Ao usar um provider assíncrono, `kickoff()` retorna um objeto `HumanFeedbackPending` em vez de lançar uma exceção:
|
|
|
|
```python Code
|
|
flow = ReviewFlow()
|
|
result = flow.kickoff()
|
|
|
|
if isinstance(result, HumanFeedbackPending):
|
|
# Flow está pausado, estado é automaticamente persistido
|
|
print(f"Aguardando feedback em: {result.callback_info['webhook_url']}")
|
|
print(f"Flow ID: {result.context.flow_id}")
|
|
else:
|
|
# Conclusão normal
|
|
print(f"Flow concluído: {result}")
|
|
```
|
|
|
|
### Retomando um Flow Pausado
|
|
|
|
Quando o feedback chega (ex: via webhook), retome o flow:
|
|
|
|
```python Code
|
|
# Handler síncrono:
|
|
def handle_feedback_webhook(flow_id: str, feedback: str):
|
|
flow = ReviewFlow.from_pending(flow_id)
|
|
result = flow.resume(feedback)
|
|
return result
|
|
|
|
# Handler assíncrono (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
|
|
```
|
|
|
|
### Tipos Principais
|
|
|
|
| Tipo | Descrição |
|
|
|------|-----------|
|
|
| `HumanFeedbackProvider` | Protocolo para providers de feedback customizados |
|
|
| `PendingFeedbackContext` | Contém todas as informações necessárias para retomar um flow pausado |
|
|
| `HumanFeedbackPending` | Retornado por `kickoff()` quando o flow está pausado para feedback |
|
|
| `ConsoleProvider` | Provider padrão de entrada bloqueante no console |
|
|
|
|
### PendingFeedbackContext
|
|
|
|
O contexto contém tudo necessário para retomar:
|
|
|
|
```python Code
|
|
@dataclass
|
|
class PendingFeedbackContext:
|
|
flow_id: str # Identificador único desta execução de flow
|
|
flow_class: str # Nome qualificado completo da classe
|
|
method_name: str # Método que disparou o feedback
|
|
method_output: Any # Saída mostrada ao humano
|
|
message: str # A mensagem de solicitação
|
|
emit: list[str] | None # Outcomes possíveis para roteamento
|
|
default_outcome: str | None
|
|
metadata: dict # Metadata customizado
|
|
llm: str | None # LLM para mapeamento de outcome
|
|
requested_at: datetime
|
|
```
|
|
|
|
### Exemplo Completo de Flow Assíncrono
|
|
|
|
```python Code
|
|
from crewai.flow import (
|
|
Flow, start, listen, human_feedback,
|
|
HumanFeedbackProvider, HumanFeedbackPending, PendingFeedbackContext
|
|
)
|
|
|
|
class SlackNotificationProvider(HumanFeedbackProvider):
|
|
"""Provider que envia notificações Slack e pausa para feedback assíncrono."""
|
|
|
|
def __init__(self, channel: str):
|
|
self.channel = channel
|
|
|
|
def request_feedback(self, context: PendingFeedbackContext, flow: Flow) -> str:
|
|
# Envia notificação Slack (implemente você mesmo)
|
|
slack_thread_id = self.post_to_slack(
|
|
channel=self.channel,
|
|
message=f"Revisão necessária:\n\n{context.method_output}\n\n{context.message}",
|
|
)
|
|
|
|
# Pausa execução - framework cuida da persistência automaticamente
|
|
raise HumanFeedbackPending(
|
|
context=context,
|
|
callback_info={
|
|
"slack_channel": self.channel,
|
|
"thread_id": slack_thread_id,
|
|
}
|
|
)
|
|
|
|
class ContentPipeline(Flow):
|
|
@start()
|
|
@human_feedback(
|
|
message="Aprova este conteúdo para publicação?",
|
|
emit=["approved", "rejected", "needs_revision"],
|
|
llm="gpt-4o-mini",
|
|
default_outcome="needs_revision",
|
|
provider=SlackNotificationProvider("#content-reviews"),
|
|
)
|
|
def generate_content(self):
|
|
return "Conteúdo de blog post gerado por IA..."
|
|
|
|
@listen("approved")
|
|
def publish(self, result):
|
|
print(f"Publicando! Revisor disse: {result.feedback}")
|
|
return {"status": "published"}
|
|
|
|
@listen("rejected")
|
|
def archive(self, result):
|
|
print(f"Arquivado. Motivo: {result.feedback}")
|
|
return {"status": "archived"}
|
|
|
|
@listen("needs_revision")
|
|
def queue_revision(self, result):
|
|
print(f"Na fila para revisão: {result.feedback}")
|
|
return {"status": "revision_needed"}
|
|
|
|
|
|
# Iniciando o flow (vai pausar e aguardar resposta do Slack)
|
|
def start_content_pipeline():
|
|
flow = ContentPipeline()
|
|
result = flow.kickoff()
|
|
|
|
if isinstance(result, HumanFeedbackPending):
|
|
return {"status": "pending", "flow_id": result.context.flow_id}
|
|
|
|
return result
|
|
|
|
|
|
# Retomando quando webhook do Slack dispara (handler síncrono)
|
|
def on_slack_feedback(flow_id: str, slack_message: str):
|
|
flow = ContentPipeline.from_pending(flow_id)
|
|
result = flow.resume(slack_message)
|
|
return result
|
|
|
|
|
|
# Se seu handler é assíncrono (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
|
|
```
|
|
|
|
<Warning>
|
|
Se você está usando um framework web assíncrono (FastAPI, aiohttp, Slack Bolt modo async), use `await flow.resume_async()` em vez de `flow.resume()`. Chamar `resume()` de dentro de um event loop em execução vai lançar um `RuntimeError`.
|
|
</Warning>
|
|
|
|
### Melhores Práticas para Feedback Assíncrono
|
|
|
|
1. **Verifique o tipo de retorno**: `kickoff()` retorna `HumanFeedbackPending` quando pausado—não precisa de try/except
|
|
2. **Use o método resume correto**: Use `resume()` em código síncrono, `await resume_async()` em código assíncrono
|
|
3. **Armazene informações de callback**: Use `callback_info` para armazenar URLs de webhook, IDs de tickets, etc.
|
|
4. **Implemente idempotência**: Seu handler de resume deve ser idempotente por segurança
|
|
5. **Persistência automática**: O estado é automaticamente salvo quando `HumanFeedbackPending` é lançado e usa `SQLiteFlowPersistence` por padrão
|
|
6. **Persistência customizada**: Passe uma instância de persistência customizada para `from_pending()` se necessário
|
|
|
|
## Documentação Relacionada
|
|
|
|
- [Visão Geral de Flows](/pt-BR/concepts/flows) - Aprenda sobre CrewAI Flows
|
|
- [Gerenciamento de Estado em Flows](/pt-BR/guides/flows/mastering-flow-state) - Gerenciando estado em flows
|
|
- [Persistência de Flows](/pt-BR/concepts/flows#persistence) - Persistindo estado de flows
|
|
- [Roteamento com @router](/pt-BR/concepts/flows#router) - Mais sobre roteamento condicional
|
|
- [Input Humano na Execução](/pt-BR/learn/human-input-on-execution) - Input humano no nível de task
|