Visão geral
Apps conversacionais tratam cada linha do usuário como uma nova execução do flow com o mesmo id de sessão. A CrewAI oferece helpers para histórico de mensagens, roteamento opcional de intenção, tracing adiado, streaming estruturado de turnos e um REPL localflow.chat().
APIs de turno
Useflow.handle_turn(message, session_id=...) para cada mensagem de usuário em REST, WebSocket, testes e UIs customizadas. Use flow.chat() quando quiser um loop de chat local no terminal para um Flow conversacional.
Flow.kickoff() não aceita os argumentos nomeados user_message= ou session_id=. Para flows conversacionais, handle_turn() guarda a mensagem pendente e chama kickoff(inputs={"id": session_id}) internamente depois de redefinir o estado de execução do turno.
handle_turn(), stream_turn() e chat() geram ValueError se o modo conversacional não estiver habilitado. Aplicar @ConversationConfig(...) o habilita automaticamente; caso contrário, defina conversational = True.
Início rápido
Streaming de um turno
Usestream_turn() quando uma UI ou um runtime precisar de eventos estruturados para um turno de chat. Ele retorna uma sessão de stream com frames ordenados para roteamento do Flow, chunks do LLM, atividade de tools e mensagens da conversa.
Ciclo de vida do turno
Cadahandle_turn executa este pipeline:
- Preparação do turno — armazena a mensagem pendente do usuário, resolve o id da sessão, redefine o acompanhamento de execução por turno e chama
kickoff(inputs={"id": session_id}). - Restauração de estado — se
inputs["id"]existe e@persistestá configurado, carrega o snapshot mais recente. FlowStarted— emitido apenas no primeiro turno da sessão adiada.- Hidratação do turno pendente — acrescenta a mensagem do usuário em
state.messages, definecurrent_user_message/last_user_messagee classifica opcionalmente quandointents/default_intents+intent_llmestão definidos. - Execução do grafo — métodos
@startdefinidos pelo usuário (se houver) →route_conversation(o start/router embutido) → o handler@listenselecionado.route_conversationtambém chama o helper sobrescrevívelconversation_start(). - Fim da execução —
flow_finishedpor turno e finalização de trace são ignorados com adiamento;Agent.kickoff()/ crews aninhados também não fecham o batch pai.
append_assistant_message(reply) quando a resposta visível não for o valor de retorno, ou ao recortar o histórico. Um retorno de string pública também é gravado como assistente e entra no snapshot @persist, então uma nova instância de Flow o restaura. A linha do usuário já é salva por handle_turn — não acrescente de novo nos handlers.
Visão geral da configuração
Decorar uma subclasse deFlow com ConversationConfig anexa os padrões de chat e habilita o modo conversacional. Consulte a referência completa de campos abaixo. Sobrescreva a pré-classificação por turno com handle_turn(..., intents=..., intent_llm=...).
Helpers ChatState de mais baixo nível
ChatState, o ConversationalConfig legado e os helpers de crewai.flow.conversation continuam disponíveis para importação em orquestração avançada, testes ou wrappers customizados. Eles são separados da API ConversationState / ConversationConfig e não adicionam os argumentos nomeados user_message= ou session_id= a Flow.kickoff().
ConversationalInputs é um TypedDict para as chaves convencionais de kickoff(inputs={...}): id, user_message, last_intent.
O ConversationState armazena messages como objetos ConversationMessage e também fornece current_user_message, ended, events e agent_threads. Use conversation_messages ao passar seu histórico canônico para um LLM.
API conversacional em Flow
Parâmetros de handle_turn
Parâmetros de kickoff
Flow.kickoff() aceita inputs, input_files, from_checkpoint e restore_from_state_id. Passe inputs={"id": session_id} quando precisar executar o flow diretamente, mas use handle_turn() quando a chamada representar uma mensagem de chat.
Atributos de instância
Métodos e propriedades
Helpers do módulo (crewai.flow.conversation)
Importáveis de crewai.flow.conversation para testes ou orquestração customizada. Esses helpers usam o formato legado de ConversationalConfig; prepare_conversational_turn() também limpa last_intent, ao contrário do handle_turn(), que o preserva como contexto do router.
Padrões de roteamento de intenção
A. Pré-classificar via ConversationalConfig (mais simples)
Defina default_intents e intent_llm. Cada handle_turn() pré-classifica a mensagem atual. Um resultado não vazio retornado por um route_turn() customizado tem precedência; caso contrário, route_conversation usa a intenção classificada do turno atual.
B. Classificar dentro de route_turn (prompts mais ricos)
Defina default_intents=None para handle_turn() apenas acrescentar a mensagem do usuário. Em route_turn(), chame classify_intent com um prompt ou descrições customizadas:
@listen("RESEARCH") (ou similar) para passos com Agent.kickoff() e ferramentas — não LLM.call() puro — quando precisar de pesquisa web ou uso multi-etapa de tools.
Quando o flow termina mas o usuário continua conversando
Cadahandle_turn() conclui uma execução do grafo, e a conversa continua com outro handle_turn() usando o mesmo session_id. Com o ciclo de vida de trace adiado padrão, essa execução emite conversation_turn_completed, enquanto FlowFinished é emitido uma vez quando finalize_session_traces() encerra a sessão. @persist restaura messages, flags e contexto.
Padrão de persistência: prefira @persist em um único passo terminal (por exemplo finalize) em vez de na classe Flow inteira. Persist em nível de classe salva após cada método; load_state usa a linha mais recente, que pode ser snapshot no meio da execução e perder atualizações dos handlers no mesmo turno.
Não use @human_feedback para linhas de chat de follow-up, a menos que um humano precise aprovar uma saída específica antes de exibi-la.
Flow conversacional
Habilite o grafo de chat conversacional definindo conversational = True em uma subclasse de Flow ou aplicando @ConversationConfig(...). O Flow base passa a fornecer route_conversation como start/router embutido, além dos listeners converse_turn e end_conversation. O listener descontinuado answer_from_history_turn permanece disponível para compatibilidade. O framework gerencia state.messages, pode acionar um LLM de roteamento e mantém o batch de trace aberto entre turnos. Você escreve as rotas customizadas; o framework cuida do resto.
Use isto quando quiser um chat multi-turno com router e handlers por rota sem cablar o ciclo de vida na mão. Use Flow[ChatState] (o padrão de mais baixo nível acima) quando precisar de controle total.
Exemplo rápido
chat():
chat() envolve handle_turn() em um REPL, sai com exit / quit, ignora linhas em branco por padrão e chama finalize_session_traces() quando a sessão termina.
ConversationConfig
Decorador de classe que anexa os defaults de chat por classe.
Sem rotas customizadas, os turnos caem em
converse. Com rotas customizadas e um LLM de conversa/router, o framework sintetiza um RouterConfig padrão; forneça um explicitamente apenas para customizar seu prompt, lista de rotas, descrições ou comportamento de fallback. Definir default_intents usa o caminho legado de pré-classificação.
Se nenhum LLM de conversa estiver configurado, o converse_turn embutido retorna um placeholder de configuração em vez de gerar uma resposta.
RouterConfig e o catálogo de rotas auto-gerado
RouterConfig.route_descriptions[label]— override explícito.Flow.builtin_route_descriptions[label]— texto canônico do framework paraconverse,ende a rota de compatibilidade descontinuadaanswer_from_history(otimizado para o LLM de routing).- O
descriptiondeclarado do método (usado por flows declarativos e projeções da DSL). - Primeira linha não vazia da docstring do handler
@listen(label). - Vazio (a rota aparece no catálogo sem descrição).
@listen("X") + uma docstring de uma linha:
RouterConfig.prompt é para enquadramento de domínio (persona do assistente, regras de negócio, voz). O catálogo de rotas é auto-gerado — não liste rotas em prompt; elas vão sair de sincronia assim que você adicionar um handler.
Nomeando handlers
A string em@listen("…") é um rótulo de rota do router (um nome de evento), e não o nome do método Python. Rótulos de rota e eventos de conclusão de métodos compartilham o mesmo namespace de gatilhos; portanto, dar ao handler o mesmo nome de sua rota faria o handler acionar a si próprio em loop.
Use um nome de método diferente — os exemplos da documentação usam o prefixo handle_*:
Rotas embutidas
Você pode sobrescrever qualquer uma definindo um handler com o mesmo nome na subclasse.
Semântica de handle_turn()
flow.handle_turn(message) roda um turno:
- Reseta o tracking por execução (
_completed_methods,_method_outputs) para o grafo re-rodar — sem isso, chamadas repetidas dekickoffna mesma instância dariam curto-circuito no turno 2+ porqueFlow.kickoff_asynctratainputs={"id": ...}como restauração de checkpoint. - Anexa a mensagem do usuário em
state.messages, definecurrent_user_message/last_user_message.last_intenté preservado do turno anterior para que o LLM de routing possa usá-lo como sinal. - Executa métodos
@startdefinidos pelo usuário (se houver), depoisroute_conversationcomo start/router embutido e, por fim, o handler@listenescolhido.route_conversationinvoca o helper sobrescrevívelconversation_start(). - O router grava sua decisão em
state.last_intent(visível para o contexto de routing do próximo turno). - Se seu handler retornou uma string e ainda não chamou
append_assistant_message,handle_turnanexa para você e persiste ostate.messagesatualizado para que a restauração@persistinclua o turno do assistente.
handle_turn() para mensagens de chat. Chamar kickoff(inputs={"id": ...}) diretamente executa o grafo sem aplicar o wrapper de turno conversacional.
chat() para REPLs locais
flow.chat() é o wrapper de terminal pronto para uso em cima de handle_turn():
- Solicita uma mensagem do usuário.
- Para com
exit/quit,EOFErrorouKeyboardInterrupt. - Chama
handle_turn(message, session_id=...). - Imprime o resultado do assistente.
- Finaliza traces de sessão adiados em um bloco
finally.
chat(defer_trace_finalization=True) habilita temporariamente a flag de adiamento na instância durante o REPL e restaura o valor anterior ao sair.
Customize o comportamento do terminal com I/O injetável:
handle_turn() diretamente.
Comportamento customizado do router
Para rodar efeitos colaterais (setup de event bus, telemetria) em toda decisão de routing, sobrescrevaroute_turn:
route_turn. Um retorno falsy não invoca _route_with_config() a partir da sua sobrescrita; o roteamento segue para a intenção pré-classificada deste turno, depois para o caminho de compatibilidade descontinuado answer_from_history quando configurado e, por fim, para converse. O last_intent do turno anterior fica disponível no contexto do router, mas nunca é repetido como fallback.
append_assistant_message e append_agent_result
Dentro de um handler @listen(label), escolha:
self.append_assistant_message(text)— adiciona um turno de assistente visível ao usuário emstate.messages. Oconverse_turndo próximo turno vai vê-lo.self.append_agent_result(agent_name, result, visibility="private")— registra um evento estruturado emstate.eventse uma thread emstate.agent_threads[agent_name]. Visibilidade pública também chamaappend_assistant_messageautomaticamente. Use resultados privados para trabalho de bastidor que não deve poluir o histórico canônico.
ConversationConfig.visible_agent_outputs pode promover globalmente os resultados privados de agentes específicos para públicos ("all" ou lista de nomes).
Declarando um flow conversacional em JSON/YAML
Um Flow declarativo também pode ser conversacional. Adicione um blococonversational no nível raiz e declare suas próprias rotas como métodos que fazem listen em um rótulo de rota:
enabled tem valor padrão true. Use enabled: false para manter a configuração e desligar o chat. Isso também desabilita a síntese de métodos embutidos, portanto a declaração deve fornecer um grafo não conversacional normal.
Três coisas são fornecidas para você:
Os campos declarativos
llm, router.llm e intent_llm aceitam um id de modelo ou um mapping de configuração, como {model: openai/gpt-4o-mini, max_tokens: 512}. O bloco conversational também aceita default_intents, visible_agent_outputs, defer_trace_finalization e os campos de RouterConfig mostrados acima. As declarações descontinuadas answer_from_history_prompt / answer_from_history_llm continuam sendo aceitas para compatibilidade.
Execute a partir do Python com as mesmas APIs de turno de um Flow conversacional baseado em classe:
Nomeando rotas
Rótulos de rota e nomes de métodos compartilham um único namespace de gatilhos, então um handler não pode ter o nome da rota que escuta —create_video escutando create_video é rejeitado na construção do flow. Use o prefixo handle_*.
O que uma declaração não consegue expressar
O
crewai run abre a TUI de chat para um flow conversacional declarativo — a mesma que um Flow conversacional em Python recebe. Um loop de chat precisa de um terminal, então uma execução headless encerra com código diferente de zero e orientações, em vez de rodar um único turno; ali, conduza pelo Python com handle_turn() ou stream_turn(). Um método declarativo com um bloco human_feedback: (Python: @human_feedback) roda em um REPL de terminal, porque o runtime coleta feedback com um prompt bloqueante que a TUI não consegue atender. O --inputs não é aceito em um flow conversacional — a entrada de cada turno é a mensagem que você digita — e retomar uma sessão por id ainda não está ligado à CLI; use flow.handle_turn(message, session_id=...) no Python para isso.
Tracing entre turnos
Comdefer_trace_finalization=True (padrão em ConversationConfig):
- Um batch de trace para toda a sessão de chat.
flow_startedsó no primeiro turno;flow_finisheduma vez emfinalize_session_traces().kickoffpor turno não exibe “Trace batch finalized”.- Trabalho aninhado (
Agent.kickoff(), crews, tools Exa) acrescenta ao batch pai; flows internos deAgentExecutornão fecham o batch da sessão cedo.
flow.chat() chama finalize_session_traces() para você. Quando você controla o loop com handle_turn(), chame finalize_session_traces() quando a sessão terminar.
suppress_flow_events=True oculta painéis Rich no console e suprime eventos de execução de métodos. Os eventos de início/fim do Flow continuam sendo emitidos, portanto o ciclo de vida externo do Flow permanece rastreável, mas os spans de métodos individuais são omitidos.
Ciclo de vida de trace do Flow conversacional
O Flow conversacional usa o mesmo ciclo de vida de tracing: defer_trace_finalization é True por padrão, então cada handle_turn() mantém o trace da sessão aberto. Turnos adiados também suprimem flow_failed por turno; em caso de erro em um turno ou encerramento antecipado da sessão, finalize a sessão explicitamente. Isso fecha o batch com o evento FlowFinished no nível da sessão, em vez de um evento FlowFailed por turno. Sempre envolva seu REPL/loop em try/finally e chame flow.finalize_session_traces() na saída. Sem isso, o batch fica aberto e a conversa final pode nunca ser exportada.
Streaming
Para UIs conversacionais, usestream_turn() e itere sobre seus objetos StreamFrame ordenados:
stream = True faz kickoff() retornar uma StreamSession. Não defina flow.stream = True ao usar handle_turn(); stream_turn() controla o ciclo de vida do streaming conversacional.
Imports
Veja também
- Dominando o Gerenciamento de Estado em Flows — persistência, estado Pydantic,
@persist - Construa Seu Primeiro Flow — fundamentos de flow
