\n\n\n\n Meu percurso na construção de uma IA conversacional com SDKs - AgntBox Meu percurso na construção de uma IA conversacional com SDKs - AgntBox \n

Meu percurso na construção de uma IA conversacional com SDKs

📖 12 min read2,351 wordsUpdated Apr 3, 2026

Oi, pessoal! Aqui é a Nina, de volta do meu laboratório digital (que, sejamos honestos, é principalmente minha mesa de cozinha com muito café morno). Hoje, quero falar sobre algo que está fazendo barulho nos meus canais Slack e que me persegue nas minhas sessões de programação tarde da noite: o mundo às vezes frustrante, frequentemente brilhante dos SDKs de IA, especialmente quando você tenta criar um assistente virtual que realmente se sinta como uma conversa, e não apenas um bot FAQ glorificado.

Minha obsessão particular nos últimos tempos tem sido integrar a API Assistants da OpenAI. Agora, sei o que vocês estão pensando: “Nina, a API Assistants? Isso é coisa do passado!” E sim, vocês não estão errados. Faz um tempo que ela está disponível. Mas escutem: não estou aqui para dar um tutorial básico de “como chamar client.beta.assistants.create()“. Todos já vimos isso. Meu objetivo hoje é falar sobre um problema específico e complicado que encontrei e sobre como lutei com o SDK para que ele fizesse o que eu queria: gerenciar conversas multi-turno com estado usando ferramentas personalizadas, sem perder a cabeça (ou o contexto do meu usuário). Pense além de um simples FAQ. Pense em fluxos de trabalho complexos, perguntas de acompanhamento e chamadas a ferramentas dinâmicas baseadas na evolução da intenção do usuário.

Meu Estado Mental: Por que a API Assistants (Continue) a Importar

Antes da API Assistants, construir algo com estado usando os modelos da OpenAI muitas vezes parecia como malabarismo com muitas bolas no ar. Você era responsável por gerenciar o histórico das mensagens, pela engenharia das prompts para o contexto e pela orquestração das chamadas às ferramentas sozinho. Isso era… exaustivo. A API Assistants prometia aliviar grande parte desse fardo, gerenciando os fios, as mensagens e até mesmo a orquestração das ferramentas. E para casos simples, ela funciona maravilhosamente.

Meu projeto atual é construir um assistente pessoal de finanças “inteligente”. Não apenas um que informe seu saldo, mas um que possa ajudá-lo a planejar orçamentos, sugerir estratégias de investimento com base em sua tolerância ao risco e até simular cenários financeiros futuros. Isso requer muitos idas e vindas, lembrar das declarações anteriores (“Quero economizar para uma casa”, “Meus rendimentos são X”, “O que aconteceria se eu investisse em Y?”), e, acima de tudo, chamar funções específicas (como buscar dados de ações em tempo real ou executar um algoritmo de projeção orçamentária) no momento certo.

Minha primeira tentativa foi um glorioso desastre. Eu estava passando todo o histórico das mensagens de um lado para o outro, tentando inserir instruções complexas na prompt do sistema e verificando manualmente se uma chamada a ferramenta era necessária. Funcionou, mas era frágil, lento e um verdadeiro pesadelo para depurar. Foi nesse momento que decidi realmente me apoiar na API Assistants e no seu SDK.

Abrace o SDK: Fios, Mensagens e o Escapista `run`

No coração da API Assistants está o conceito de `Threads` e `Messages`. Você cria um `Thread` para uma conversa, adiciona `Messages`, e então “executa” o `Assistant` nesse `Thread`. O `Assistant` então processa as mensagens, decide se deve chamar uma ferramenta e gera uma resposta. Simples, não?

Aqui é onde isso se torna interessante. Meu assistente financeiro precisa fazer coisas como:

  • Rastrear objetivos: “Quero economizar 50.000 dólares para um sinal de entrada.”
  • Coletar informações: “Qual é sua renda mensal atual?”
  • Executar cálculos: “Com base nisso, quanto tempo levará se eu economizar 1.000 dólares por mês?”
  • Fornecer conselhos: “Considere diversificar seu portfólio com fundos de índice de baixo custo.”

Cada uma dessas tarefas frequentemente requer uma chamada a uma ferramenta personalizada. Por exemplo, `calculate_savings_timeline(goal_amount, monthly_income, monthly_savings)`. O desafio não é apenas definir a ferramenta; é fazer com que o Assistant:

  1. Reconheça que ele precisa da ferramenta.
  2. Identifique todos os parâmetros necessários (mesmo que estejam distribuídos em várias mensagens do usuário).
  3. Peça aos usuários os parâmetros faltantes com delicadeza.
  4. Execute a ferramenta e incorpore os resultados.

O Problema dos Parâmetros: Quando o Assistant Pede Mais

Vamos supor que um usuário diga: “Quanto tempo vou levar para economizar para uma casa?” Minha ferramenta `calculate_savings_timeline` precisa de `goal_amount`, `monthly_income`, e `monthly_savings`. O Assistant, sendo inteligente, frequentemente perceberá que faltam informações. O SDK, através do objeto `run`, lhe dirá que ele `requires_action`.

Minha abordagem inicial era simplesmente aguardar o status `requires_action` e então apresentar uma genérica “Quais informações você precisa?” ao usuário. Isso era desajeitado. O usuário muitas vezes não sabia quais parâmetros eram necessários para a ferramenta. Uma abordagem melhor, que acabei encontrando, foi inspecionar as `tool_calls` no status `requires_action`.


def submit_tool_outputs(client, thread_id, run_id, tool_outputs):
 return client.beta.threads.runs.submit_tool_outputs(
 thread_id=thread_id,
 run_id=run_id,
 tool_outputs=tool_outputs
 )

# ... na sua loop de conversa ...

if run.status == 'requires_action':
 tool_outputs = []
 missing_params = {}
 for tool_call in run.required_action.submit_tool_outputs.tool_calls:
 function_name = tool_call.function.name
 arguments = json.loads(tool_call.function.arguments)

 # Aqui está a sacada: verifique se um parâmetro necessário está faltando
 # Assumimos que suas ferramentas estão definidas com um esquema
 # Para simplificar, digamos que sabemos que 'monthly_savings' é sempre necessário
 if function_name == "calculate_savings_timeline" and "monthly_savings" not in arguments:
 missing_params['monthly_savings'] = 'Quanto você planeja economizar a cada mês?'
 
 # ... verificação de parâmetros mais sofisticada aqui ...

 if missing_params:
 # Informe o usuário sobre o que está faltando
 user_prompt = "Preciso de um pouco mais de informações para ajudá-lo. "
 for param, question in missing_params.items():
 user_prompt += f"{question} "
 return {"status": "waiting_for_user_input", "prompt": user_prompt}
 else:
 # Todos os parâmetros estão presentes, execute a ferramenta
 output = execute_tool_function(function_name, arguments) # Sua função para executar a lógica da ferramenta
 tool_outputs.append({
 "tool_call_id": tool_call.id,
 "output": json.dumps(output)
 })
 
 if tool_outputs:
 run = submit_tool_outputs(client, thread.id, run.id, tool_outputs)
 # Continue a consultar o run até que ele esteja concluído ou precise de uma nova ação
 else:
 # Este caso lida com se detectamos parâmetros faltantes e pedimos ao usuário
 pass

Este código é uma versão simplificada, mas a ideia principal é interceptar `requires_action`, observar o que o Assistant quer fazer e, se faltarem argumentos, perguntar especificamente ao usuário. Isso torna a conversa muito mais natural, como um humano fazendo perguntas de esclarecimento, ao invés de um genérico “erro”.

O Estado Escorregadio: Manter um Registro entre os Turnos

Uma das maiores dores de cabeça era garantir que, se um usuário fornecesse uma informação (“Meus rendimentos são de 5.000 dólares por mês”), o Assistant se lembrasse disso para chamadas às ferramentas posteriores dentro da mesma conversa, mesmo que não fosse utilizada imediatamente. A API Assistants lida com isso bastante bem dentro de um `Thread`, mantendo o histórico das mensagens. No entanto, às vezes, você quer guiá-lo explicitamente ou fornecer informações que não são parte de uma mensagem direta do usuário.

Eu acabei utilizando `metadata` nos objetos `Thread` e `Message` mais do que eu inicialmente pensei. Por exemplo, se o usuário indicar explicitamente sua tolerância ao risco, eu poderia armazenar isso nas metadados do thread. Embora o Assistant não “leia” diretamente essas metadados em seu processo de raciocínio, elas são inestimáveis para a lógica da minha aplicação quando preciso pré-preencher argumentos para chamadas às ferramentas ou tomar decisões sobre quais ferramentas são pertinentes.

Outro modelo que adotei foi injetar mensagens “semelhantes a sistemas” no fluxo se eu precisasse lembrar explicitamente ao Assistente alguns fatos derivados de uma chamada de ferramenta ou de um processo interno. Por exemplo, depois que `calculate_savings_timeline` retornou que levaria 10 anos, eu poderia adicionar uma mensagem como: `{“role”: “user”, “content”: “O plano de poupança do usuário indica um prazo de 10 anos para alcançar seu objetivo.”}`. Isso não vem diretamente do usuário, mas ajuda a reforçar o contexto para as respostas seguintes do Assistente.

Gerenciamento da Saída da Ferramenta e Ações Seguintes

Quando uma ferramenta é executada, você obtém uma saída. O Assistente então pega essa saída e gera uma resposta. Mas o que acontece se a saída da ferramenta em si suscita um novo conjunto de perguntas ou uma nova chamada de ferramenta? Por exemplo, a ferramenta `calculate_savings_timeline` poderia retornar que o objetivo é inatingível com as economias atuais. O Assistente deveria idealmente sugerir, “Talvez devêssemos explorar maneiras de aumentar suas economias mensais ou reduzir seu valor alvo.” Não se trata apenas de saída de texto; trata-se de encadear etapas lógicas.

O SDK da API Assistants lida com isso admiravelmente ao manter o objeto `run` ativo. Depois que você faz `submit_tool_outputs`, o `run` frequentemente muda para `in_progress`, e então pode gerar um novo `requires_action` (para outra chamada de ferramenta) ou `completed`. Meu loop principal para gerenciar o fluxo de conversa se parece com algo assim:


def obter_resposta_assistente(client, thread_id, assistant_id, mensagem_usuario):
 client.beta.threads.messages.create(
 thread_id=thread_id,
 role="user",
 content=mensagem_usuario
 )

 run = client.beta.threads.runs.create(
 thread_id=thread_id,
 assistant_id=assistant_id
 )

 while run.status in ['queued', 'in_progress', 'cancelling']:
 time.sleep(1) # Seja um bom cidadão, não sobrecarregue a API
 run = client.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id)

 if run.status == 'completed':
 messages = client.beta.threads.messages.list(thread_id=thread_id, order="desc")
 for msg in messages.data:
 if msg.role == "assistant":
 # Encontrar a última mensagem do assistente, geralmente a primeira
 return msg.content[0].text.value # Supondo que o conteúdo seja textual
 return "Nenhuma resposta do assistente."

 elif run.status == 'requires_action':
 saídas_ferramenta = []
 for tool_call in run.required_action.submit_tool_outputs.tool_calls:
 nome_funcao = tool_call.function.name
 argumentos = json.loads(tool_call.function.arguments)
 
 # Aqui, sua lógica personalizada para lidar com parâmetros ausentes ou executar ferramentas
 # Para este exemplo simplificado, presumamos que todos os parâmetros estão presentes e que estamos executando
 print(f"O assistente quer chamar {nome_funcao} com args : {argumentos}")
 
 saída = executar_funcao_ferramenta(nome_funcao, argumentos) # Sua lógica de execução de ferramenta
 saídas_ferramenta.append({
 "tool_call_id": tool_call.id,
 "output": json.dumps(saída)
 })
 
 # Submeter as saídas das ferramentas e continuar a execução
 run = client.beta.threads.runs.submit_tool_outputs(
 thread_id=thread_id,
 run_id=run.id,
 tool_outputs=saídas_ferramenta
 )
 # Chame esta função recursivamente para processar a execução atualizada
 # Cuidado com a profundidade de recursão em produção, você pode precisar de um loop
 return obter_resposta_assistente(client, thread_id, assistant_id, "") # Passar uma mensagem vazia
 
 elif run.status == 'failed':
 return f"A execução do assistente falhou: {run.last_error.message}"
 
 else:
 return f"Status de execução inesperado: {run.status}"

Esta função `obter_resposta_assistente`, embora simplificada, mostra o loop central. O essencial é que `requires_action` não é um beco sem saída. É uma oportunidade para sua aplicação intervir, executar a ferramenta solicitada e então retornar os resultados ao Assistente para continuar seu raciocínio. Esse feedback em loop fechado é o que torna possíveis conversas verdadeiramente dinâmicas e escaláveis.

Pontos a Lembrar para Seu Próximo Projeto de Assistente IA

  1. Adoção do ciclo de vida `run.status`: Não se contente em verificar apenas se está `completed`. `requires_action` é seu amigo, não um erro. Crie manipuladores sólidos para cada status relevante.
  2. Inspecione `tool_calls` para parâmetros ausentes: Em vez de simples indicações, mergulhe em `run.required_action.submit_tool_outputs.tool_calls` para entender *exatamente* o que o Assistente precisa. Isso melhora sua experiência do usuário.
  3. Uso estratégico de mensagens do tipo sistema: Se sua lógica de aplicação interna deduz novos fatos ou precisa ressaltar certos contextos, considere injetar mensagens “usuário” que representem esses fatos. O Assistente tratará como parte do histórico do fluxo.
  4. Metadados para o estado da aplicação: Embora o Assistente não possa “ler” diretamente os metadados dos fluxos ou mensagens para seu raciocínio, é um local poderoso para sua aplicação armazenar e recuperar estados relevantes à conversa. Pense nas preferências do usuário, nas variáveis de sessão atuais, etc.
  5. Teste casos específicos para a orquestração de ferramentas: O que acontece se uma ferramenta falhar? O que acontece se o usuário mudar de ideia durante a invocação de uma ferramenta? Projete sua `executar_funcao_ferramenta` e gerencie erros no loop `requires_action` com cuidado.

Construir agentes IA conversacionais realmente inteligentes não diz respeito apenas à escolha do último modelo. Trata-se de manusear habilmente os SDKs que eles fornecem para lidar com as complexidades da interação humana. A API dos Assistentes OpenAI, com um pouco de engenharia reflexiva em torno de sua gestão de estado e da orquestração de ferramentas, pode realmente aprimorar sua experiência conversacional além de uma simples troca. É ainda uma jornada e eu ainda estou aprendendo novas dicas, mas espero que essas ideias evitem algumas noites em claro e cafés frios!

Artigos Relacionados

🕒 Published:

🧰
Written by Jake Chen

Software reviewer and AI tool expert. Independently tests and benchmarks AI products. No sponsored reviews — ever.

Learn more →
Browse Topics: AI & Automation | Comparisons | Dev Tools | Infrastructure | Security & Monitoring

Related Sites

AidebugAgnthqAgntkitAgent101
Scroll to Top