Skip to main content

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 local flow.chat().

APIs de turno

Use flow.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

Use stream_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.
Para o contrato completo dos frames e a lista de canais, consulte Contrato do Runtime de Streaming.

Ciclo de vida do turno

Cada handle_turn executa este pipeline:
  1. 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}).
  2. Restauração de estado — se inputs["id"] existe e @persist está configurado, carrega o snapshot mais recente.
  3. FlowStarted — emitido apenas no primeiro turno da sessão adiada.
  4. Hidratação do turno pendente — acrescenta a mensagem do usuário em state.messages, define current_user_message / last_user_message e classifica opcionalmente quando intents / default_intents + intent_llm estão definidos.
  5. Execução do grafo — métodos @start definidos pelo usuário (se houver) → route_conversation (o start/router embutido) → o handler @listen selecionado. route_conversation também chama o helper sobrescrevível conversation_start().
  6. Fim da execuçãoflow_finished por turno e finalização de trace são ignorados com adiamento; Agent.kickoff() / crews aninhados também não fecham o batch pai.
Os handlers devem chamar 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 de Flow 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:
Use @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

Cada handle_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

Para um chat local no terminal, use 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.
answer_from_history_prompt, answer_from_history_llm e a rota answer_from_history estão descontinuados e serão removidos em uma versão futura. Eles duplicam converse, que já recebe o histórico canônico, adicionam uma chamada de LLM para verificar elegibilidade e são ignorados quando o auto-router normal retorna uma rota. As configurações existentes continuam funcionando e emitem DeprecationWarning.
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

O prompt do router é montado automaticamente. Para cada rota o framework escolhe a descrição nesta precedência:
  1. RouterConfig.route_descriptions[label] — override explícito.
  2. Flow.builtin_route_descriptions[label] — texto canônico do framework para converse, end e a rota de compatibilidade descontinuada answer_from_history (otimizado para o LLM de routing).
  3. O description declarado do método (usado por flows declarativos e projeções da DSL).
  4. Primeira linha não vazia da docstring do handler @listen(label).
  5. Vazio (a rota aparece no catálogo sem descrição).
Na prática, adicionar uma rota é @listen("X") + uma docstring de uma linha:
…e o LLM de routing vê:
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_*:
Não replique o rótulo da rota no método:

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:
  1. Reseta o tracking por execução (_completed_methods, _method_outputs) para o grafo re-rodar — sem isso, chamadas repetidas de kickoff na mesma instância dariam curto-circuito no turno 2+ porque Flow.kickoff_async trata inputs={"id": ...} como restauração de checkpoint.
  2. Anexa a mensagem do usuário em state.messages, define current_user_message / last_user_message. last_intent é preservado do turno anterior para que o LLM de routing possa usá-lo como sinal.
  3. Executa métodos @start definidos pelo usuário (se houver), depois route_conversation como start/router embutido e, por fim, o handler @listen escolhido. route_conversation invoca o helper sobrescrevível conversation_start().
  4. O router grava sua decisão em state.last_intent (visível para o contexto de routing do próximo turno).
  5. Se seu handler retornou uma string e ainda não chamou append_assistant_message, handle_turn anexa para você e persiste o state.messages atualizado para que a restauração @persist inclua o turno do assistente.
Chame 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():
Ele cobre o loop local comum:
  1. Solicita uma mensagem do usuário.
  2. Para com exit / quit, EOFError ou KeyboardInterrupt.
  3. Chama handle_turn(message, session_id=...).
  4. Imprime o resultado do assistente.
  5. 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:
Para apps web, workers em background, testes e transportes customizados, continue usando handle_turn() diretamente.

Comportamento customizado do router

Para rodar efeitos colaterais (setup de event bus, telemetria) em toda decisão de routing, sobrescreva route_turn:
Para ignorar completamente o router LLM e escolher uma rota programaticamente, retorne uma string não vazia de 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 em state.messages. O converse_turn do próximo turno vai vê-lo.
  • self.append_agent_result(agent_name, result, visibility="private") — registra um evento estruturado em state.events e uma thread em state.agent_threads[agent_name]. Visibilidade pública também chama append_assistant_message automaticamente. 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 bloco conversational no nível raiz e declare suas próprias rotas como métodos que fazem listen em um rótulo de rota:
Declarar o bloco já é o opt-in — 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

Com defer_trace_finalization=True (padrão em ConversationConfig):
  • Um batch de trace para toda a sessão de chat.
  • flow_started só no primeiro turno; flow_finished uma vez em finalize_session_traces().
  • kickoff por turno não exibe “Trace batch finalized”.
  • Trabalho aninhado (Agent.kickoff(), crews, tools Exa) acrescenta ao batch pai; flows internos de AgentExecutor nã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, use stream_turn() e itere sobre seus objetos StreamFrame ordenados:
Para um Flow não conversacional, definir 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