Como saí das areias movediças do código com um agente de IA
Mergulhei na construção de um chatbot com zero habilidades de programação e muito entusiasmo — só para descobrir que minha v0.1 era um desastre de bancos de dados CSV e chunking primitivo, até que agentes de IA me salvaram.
Atualização (2026): Este chatbot evoluiu para Sydney! Depois de muitas iterações, Sydney agora vive em /ask/ e foca em conteúdo do blog e produtos.
No início de novembro do ano passado, lancei a versão 0.1 do meu chatbot DIY versão 0.1. Na época, escrevi: "Embora a v0.1 representasse um grande primeiro passo como iniciante em programação, ela tinha limitações significativas." Bem, como se revelou, isso é um eufemismo. Eu não tinha noção de como o processo inteiro e minha construção eram rudimentares. A realidade era que minha tentativa inicial, embora sincera, era mais um protótipo montado com entusiasmo mas conhecimento limitado.
Este post não é apenas um follow-up; é um mergulho profundo na jornada que se desenrolou a partir daquele ponto — uma jornada cheia de tentativas, erros e lições inestimáveis. Estou expondo os detalhes dessa aventura, não apenas pela transparência, mas na esperança de que minhas experiências, por mais detalhadas que sejam, possam ressoar ou até ajudar alguém em um caminho semelhante. (Para contexto adicional, sou um profissional de publicidade de meia-idade com zero experiência prévia em programação.)
Como mencionado, para lançar a v0.1 do meu chatbot, segui principalmente as instruções deste curso curto "Building Systems with the ChatGPT API" e dois cookbooks da OpenAI: Question answering using embeddings-based search e How to count tokens with tiktoken.
Por que meu chatbot v0.1 é terrível:
- Chunking: Simplesmente dividi posts longos de blog em pedaços menores com base no comprimento do token, ou seja, um simples chunk estático de dados por caracteres. A forma mais primitiva possível de dividir :D. Se quiser entender por que essa é uma ideia terrível, leia sobre os 5 níveis de divisão de texto de Greg Kamradt aqui.
- Embedding: Tive dificuldades com embeddings. Usei o modelo de embedding da OpenAI, mas continuava atingindo limites de requisições da API, fazendo o processo de embedding falhar no meio. Aprendi então a fazer requisições em lote e adicionar timeouts entre eles para evitar os limites. Por fim, salvei os embeddings gerados em um arquivo .csv simples como meu "banco de dados" improvisado.
- Banco de dados: Sabia que um CSV não era ideal para um banco de dados, mas não tinha habilidades para alternativas melhores.
- Metadata: Inicialmente não percebi que incluir metadados como datas de publicação e URLs dos posts era importante para que chatbots respondessem com precisão às perguntas dos usuários. Tive que repetir o embedding e o salvamento para incorporar metadados relevantes.
- Retriever: Não estava ciente dos diferentes tipos de retrievers e algoritmos. Simplesmente usei a busca por relevância da OpenAI para recuperar um número fixo de resultados.
- Memória: Para ter uma conversa, o chatbot precisa se lembrar do que o usuário disse anteriormente. E aqui estava o problema — com uma janela de contexto limitada do gpt-3.5 (na época), havia uma clara troca entre tamanho do chunk e quantos resultados você quer recuperar.
- Por exemplo, se o tamanho do chunk é 800 tokens e o retriever retorna os 8 melhores resultados, são 6.400 tokens ou mais de 50% do limite do modelo antigo.
- O acima é apenas 1 pergunta, então imagina uma conversa de múltiplos turnos e como a memória pode se encher muito rapidamente.
- Uma forma de resolver esse problema é ter um tamanho de chunk menor e o retriever retornar menos resultados, mas com um retriever básico (acima), isso significa que o modelo não tem informações abrangentes para responder à pergunta.
- Eu nem mesmo usava nenhuma IDE. Todos os códigos foram editados usando o TextEdit no Mac :D (Mencionei que era um novato antes? :P)
- Posso continuar, mas acho que você entendeu o quadro.
Meu "vale da morte"
Ansioso para ir além das limitações da v0.1, tentei vários cursos online, esperando que eles fornecessem as peças que faltavam para elevar minhas habilidades. Mas o progresso só levou a becos sem saída.
Lutei com exercícios em bancos de dados vetoriais (Vector Databases: from Embeddings to Applications with Weaviate), métodos RAG avaliativos (Building and Evaluating Advanced RAG Applications com Llama-Index e Truera) e técnicas avançadas de recuperação (Advanced Retrieval for AI with Chroma.) Por mais que tentasse, não conseguia conectar a teoria à aplicação prática usando meus próprios dados de blog.
Os cursos eram mal projetados? Não — a deficiência era minha falta de conhecimento de base. Mesmo assim, falhar repetidamente era massivamente frustrante, sem falar em desmoralizante. Eu me via em um vale figurativo, sem saber como seguir em frente.
Vitórias incrementais ao longo do tempo
Pepitas de valor surgiram de falhas repetidas, porém:
-
Adoção do VS Code em vez do TextEdit
-
Aproveitamento das extensões do GitHub Copilot
-
Apreciação do Jupyter Notebook para ambientes de desenvolvimento
O último curso mencionou LangChain, um novo framework popular para construção de chatbots. Na verdade, tentei tutoriais de LangChain meses atrás ("LangChain: Chat with Your Data" e "Functions, Tools and Agents with LangChain") sem muito sucesso. Mas agora, com conhecimento arduamente adquirido, revisitar sua documentação se mostrou esclarecedor. Os conceitos se encaixaram e sua arquitetura modular fez sentido intuitivo.
Consegui imaginar adaptar as robustas capacidades do LangChain ao meu projeto de paixão. Finalmente, um caminho a seguir se revelou! Passo a passo, me familiarizei com seus pipelines para ingestão de dados, embedding, armazenamento e recuperação.
Minha confiança cresceu com cada peça que consegui implementar. A v2 começou a tomar forma...
Reconstruindo a base
Com o Langchain como guia, me propus a reconstruir meu chatbot do zero:
Ingestão de exportações do WordPress em JSON
Depois de algumas tentativas e ajustes nos parâmetros de ingestão, o JSONLoader do LangChain analisou corretamente meus posts exportados. Agora a entrada validada poderia alimentar os pipelines downstream. (O código para esta etapa está no arquivo "DataIngestionAndIndexing.ipynb" neste repositório público do Github.)
Divisão automática de texto
Meu chunking ingênuo por comprimento de token foi substituído pelo SentenceTransformers do LangChain, usando NLP avançado para dividir unidades semânticas. Chega de frases cortadas no meio! As configurações mantiveram os chunks em tamanho apropriado para modelos com restrições de memória.
from langchain.text_splitter import SentenceTransformersTokenTextSplitter
# Define the token splitter with specific configurations
token_splitter = SentenceTransformersTokenTextSplitter(
chunk_overlap=0, # Overlap between chunks
tokens_per_chunk=256 # Number of tokens per chunk
)
# Split the documents into chunks based on tokens
all_splits = token_splitter.split_documents(documents)
print(f"Total document splits: \{len(all_splits)\}")
Gerando embeddings e índices
As dificuldades passadas com os limites da API da OpenAI desapareceram usando o wrapper do LangChain para embeddings da OpenAI. Compactados em duas linhas de código, os embeddings extraíram claramente as características salientes do texto dividido.
Para o vector store, optei pelo FAISS (Facebook AI Similarity Search) em vez do Weaviate ou Chroma. O FAISS testado pela indústria alcançou o equilíbrio certo entre capacidade e complexidade para minhas necessidades. Sua versão CPU indexou rapidamente os chunks do blog, produzindo um banco de dados compacto e pesquisável. Não preciso mais me preocupar com lotes ou atingir o limite de requisições da API para a OpenAI.
O Langchain suporta múltiplos vector stores, então você pode verificá-los aqui.
# Initialize embeddings and FAISS vector store
embeddings = OpenAIEmbeddings()
db = FAISS.from_documents(all_splits, embeddings)
# Save the vector store locally
db.save_local("path/to/save/faiss_index") # Placeholder for save path/ index name
Duas linhas de código! É isso.
Também percebi que se seu conteúdo não for pequeno (tenho mais de 400 posts de blog), não tente usar o retriever imediatamente após o embedding, porque os índices precisam de um tempo para ser concluídos/se tornarem estáveis.
Avaliando Retrievers
Configurar este retriever leva apenas 1 linha de código :D, usando FAISS como vector store.
retriever = db.as_retriever(search_type="mmr")
Com meu índice e retriever definidos, tinha pipelines de dados prontos para alimentar um chatbot inteligente!
Arquitetando agentes conversacionais
Decidi usar o framework de agentes do langchain para construir este chatbot. É exagero neste ponto? Sim, é. Mas minha esperança é que ao longo do tempo, eu possa evoluir este chatbot e dar a ele mais "ferramentas" ou funcionalidades. O Langchain torna super fácil configurar o agente e dar a ele ferramentas para usar.
embeddings = OpenAIEmbeddings()
db = FAISS.load_local("path/to/your/faiss_index_file", embeddings) # Replace the path with your actual FAISS index file path
retriever = db.as_retriever(search_type="mmr")
tool = create_retriever_tool(
retriever,
"search_your_blog", # Replace "search_your_blog" with a descriptive name for your tool
"Your tool description here" # Provide a brief description of what your tool does
)
tools = [tool]
prompt_template = ChatPromptTemplate.from_messages([
# Customize the prompt template according to your chatbot's persona and requirements
])
llm = ChatOpenAI(model_name="gpt-3.5-turbo-1106", temperature=0)
llm_with_tools = llm.bind_tools(tools)
agent = (
\{
"input": lambda x: x["input"],
"agent_scratchpad": lambda x: format_to_openai_tool_messages(x["intermediate_steps"]),
"chat_history": lambda x: x["chat_history"],
\}
| prompt_template
| llm_with_tools
| OpenAIToolsAgentOutputParser()
)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
O código Python completo e final para o agente
Por último mas não menos importante, se você quiser experimentar o chatbot v2, aqui está.
É estranho que o chatbot não saiba nada sobre você, Chandler?
P.S: Obrigado a alguns de vocês que entraram em contato para me dizer que o chatbot não sabe nada sobre mim. E vocês estão certos! É porque esqueci de exportar a página "Sobre" e só exportei os "posts publicados". Esta é a segunda vez que esqueço de fazer isso, então vou incluir perguntas básicas sobre mim na lista de perguntas de avaliação. Lição aprendida!
Obrigado a todos por compartilharem seus feedbacks construtivos. Continue mandando. E sim, eu sei que o chatbot é muito lento para iniciar, então estou trabalhando nisso também. :| (Mencionei que sou um novato antes? :P)
Uma atualização rápida
O problema de o chatbot não saber nada sobre mim agora está resolvido. Isso é o que fiz e aprendi:
- Exportar a página "Sobre mim" do Wordpress para .XML conforme acima.
- Realizei a divisão de texto e gerei embeddings usando FAISS como acima. Salvei o vector store com um nome diferente localmente para testar o retriever
# save the vector store to local machine
db.save_local("faiss_index_about")
# set up the retriever to test the new vector store about Chandler
from langchain.retrievers.multi_query import MultiQueryRetriever
llm = ChatOpenAI(temperature=0)
retriever_from_llm = MultiQueryRetriever.from_llm(
retriever=db.as_retriever(), llm=llm
)
# test retriever
question = "Who is Chandler Nguyen?"
results = retriever_from_llm.get_relevant_documents(query=question, top_k=8)
for doc in results:
print(f"Content: {doc.page_content}, Metadata: {doc.metadata}")
- Acontece que o processo para mesclar dois vector stores FAISS é surpreendentemente simples, conforme a documentação aqui.
# Try to merge two FAISS vector stores into 1
# load the vector store from local machine
from langchain_community.vectorstores import FAISS
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
db_1 = FAISS.load_local("faiss_index_about", embeddings)
db_2 = FAISS.load_local("faiss_index", embeddings)
db_2.merge_from(db_1)
# save the vector store to local machine
db_2.save_local("faiss_index_v2")
# test the new vector store to confirm correct retrieved documents
retriever = db_2.as_retriever(search_type="mmr")
results = retriever.get_relevant_documents("Who is Chandler Nguyen?")
for doc in results:
print(f"Content: {doc.page_content}, Metadata: {doc.metadata}")
- Depois disso, é basicamente o mesmo processo acima, usando o novo vector store
Atualização de 14 de fevereiro: Chatbot v2.10 revelado
Duas semanas após essa implantação do chatbot, introduzi a versão 2.10 que eleva a experiência do usuário com velocidade, escalabilidade e simplicidade aprimoradas. Você pode ler mais sobre isso aqui.
Atualização de 25 de março: De upgrades de frontend às lutas e avanços com Docker
Você pode ler mais sobre isso aqui.





