Construindo um assistente pessoal 100% local com Ollama, LangChain e voz — e as armadilhas que ninguém conta A developer built a fully local personal assistant using Ollama, LangChain, and voice tools, running entirely on a laptop without cloud API calls. The project revealed significant challenges in tool calling with smaller local LLMs, including unreliable structured output and models writing scripts instead of executing actions. Key lessons included avoiding forced tool calling for long-form content and pre-processing certain requests to bypass model decision-making. Nos últimos dias montei um assistente pessoal do zero rodando 100% na minha máquina — sem chamadas pra API paga, sem dados saindo do notebook. Ele pesquisa na internet, escreve e salva código, entende voz, responde falando, tem memória de longo prazo e roda um LLM local via Ollama https://ollama.com . O resultado funciona bem, mas o caminho até aqui expôs um monte de conceitos que todo mundo que trabalha com LLMs locais/agentes acaba esbarrando — principalmente a diferença brutal entre "um LLM de 70B+ na nuvem" e "um LLM de 8-14B rodando numa GPU de laptop". Esse artigo é sobre esses conceitos. Voz do usuário │ ▼ VAD detecção de fala ──► Whisper fala → texto │ ▼ Agente LangChain/LangGraph ──► Ollama LLM local │ │ │ ├─► Tools: busca web, clima, memória... ▼ │ Piper texto → fala ◄─────────────┘ │ ▼ Áudio de resposta Tudo roda localmente: o LLM via Ollama, a transcrição via faster-whisper https://github.com/SYSTRAN/faster-whisper , a síntese de voz via Piper https://github.com/OHF-Voice/piper1-gpl , e um SQLite guardando memória. Nada trafega pra fora da máquina, exceto as buscas na web em si. Um "agente" de IA não é magia — é um LLM rodando dentro de um loop que pode, a cada passo, decidir entre responder em texto ou chamar uma função "tool" , observar o resultado, e decidir de novo. O LangChain 1.0 https://python.langchain.com formaliza isso com create agent , construído em cima do LangGraph, que modela esse loop como uma máquina de estados. Cada "tool" é só uma função Python decorada, com uma docstring que vira a descrição que o modelo lê pra decidir quando usá-la: php @tool def get weather city: str - str: """Retorna o clima atual de uma cidade...""" ... O modelo nunca executa a função diretamente — ele gera uma estrutura "tool call" dizendo "quero chamar get weather com city='São Paulo' ", o framework executa de verdade e devolve o resultado pro modelo continuar. Isso foi a maior fonte de dor de cabeça do projeto. Tool calling depende do modelo ter sido treinado pra gerar uma estrutura específica geralmente JSON num formato que o runtime Ollama, no meu caso sabe interpretar. Só que: