- É possível restringir as respostas de modelos locais para que sigam um JSON Schema, reduzindo o esforço de parsing no pós-processamento e tornando os resultados de extração de dados mais consistentes
- Usuários solicitam saídas estruturadas da mesma forma em cURL, Python e JavaScript, passando o schema no parâmetro
format
- Em Python, usar Pydantic em conjunto, e em JavaScript, Zod, facilita conectar a definição do schema e a validação da resposta no nível do código
- Os exemplos retornam e validam informações de países, texto sobre pets e resultados de análise de imagens com
llama3.2-vision em campos JSON definidos
- Para resultados estáveis, recomenda-se incluir “return as JSON” no prompt e reduzir a temperature para 0
Restringindo respostas do modelo com JSON Schema
- O Ollama oferece suporte a saídas estruturadas que restringem a saída do modelo para que siga um formato específico de JSON Schema
- As bibliotecas Python e JavaScript do Ollama também foram atualizadas para oferecer suporte a saídas estruturadas
- Esse recurso pode ser usado nas seguintes tarefas
- Parsing de dados a partir de documentos
- Extração de dados a partir de imagens
- Estruturação de respostas de modelos de linguagem
- Obtenção de maior confiabilidade e consistência do que no JSON mode
Instalação e forma de chamada
- É necessário baixar a versão mais recente do Ollama
- A biblioteca Python é atualizada para a versão mais recente com o comando abaixo
pip install -U ollama
- A biblioteca JavaScript é instalada com o comando abaixo
npm i ollama
- Ao solicitar uma saída estruturada, passe o schema no parâmetro
format em uma requisição cURL ou nas bibliotecas Python·JavaScript
Passando JSON Schema com cURL
- O exemplo em cURL envia uma requisição para
http://localhost:11434/api/chat, com model definido como llama3.1 e stream como false
- Em
format, é inserido um schema de objeto com name, capital e languages
name: string
capital: string
languages: array de strings
- Os três campos são marcados como
required
- A resposta é retornada no formato de JSON Schema incluído na requisição
{
"capital": "Ottawa",
"languages": [
"English",
"French"
],
"name": "Canada"
}
Uso das bibliotecas Python e JavaScript
-
Python
- A biblioteca Ollama para Python passa o schema como um objeto JSON no parâmetro
format
- O schema pode ser passado como
dict, e a forma recomendada é serializá-lo com model_json_schema() do Pydantic
- O exemplo define os campos
name, capital e languages no modelo Country e valida a resposta com Country.model_validate_json()
country = Country.model_validate_json(response.message.content)
- A saída de exemplo é retornada com
Canada, Ottawa, English e French de acordo com os campos definidos
-
JavaScript
- A biblioteca Ollama para JavaScript passa o schema como um objeto JSON no parâmetro
format
- O schema pode ser passado como
object, e a forma recomendada é usar Zod com zodToJsonSchema()
- O exemplo define o schema
Country como um objeto Zod, faz JSON.parse() do corpo da resposta e depois valida com Country.parse()
const country = Country.parse(JSON.parse(response.message.content));
Extração de dados a partir de texto e imagem
-
Extração de texto sobre pets
- Saídas estruturadas podem ser usadas para extrair informações necessárias de texto
- O exemplo define os modelos Pydantic
Pet e PetList para retornar informações de pets em uma estrutura JSON
Pet: name, animal, age, color, favorite_toy
PetList: array pets
- O texto de entrada contém informações sobre dois gatos
- Luna: 5 anos, pelo cinza, gosta de yarn
- Loki: 2 anos, preto, gosta de tennis balls
- A saída é validada como uma lista de objetos
Pet de acordo com o schema definido
-
Descrição de imagem com modelo de visão
- Saídas estruturadas podem ser usadas junto com modelos de visão
- O exemplo analisa uma imagem com
llama3.2-vision e retorna um resultado de acordo com o schema ImageDescription
- O schema inclui os seguintes campos
summary
objects
scene
colors
time_of_day
setting
text_content
- A requisição de exemplo instrui o modelo a analisar objetos, cena, cores e texto detectável na imagem
- Nas opções,
temperature é definido como 0 para usar uma saída mais determinística
- A saída de exemplo resume uma cena de palmeiras na praia e retorna os objetos
tree e beach, cores, período do dia e ambiente externo em campos estruturados
Uso da API compatível com OpenAI
- O exemplo compatível com OpenAI configura o cliente
OpenAI com base_url="http://localhost:11434/v1" e api_key="ollama"
- Usa
client.beta.chat.completions.parse() para passar o modelo Pydantic PetList em response_format
- Verifica
completion.choices[0].message na resposta e, se houver parsed, imprime o resultado parseado
- Se houver
refusal, imprime a resposta de recusa, e openai.LengthFinishReasonError é tratado como caso de excesso de tokens
Configurações para saída estável e planos futuros
- Para definir schemas de resposta, recomenda-se usar Pydantic em Python ou Zod em JavaScript
- É recomendável adicionar “return as JSON” ao prompt para que o modelo entenda a solicitação
- Para uma saída mais determinística, defina temperature como 0
- Os planos futuros são os seguintes
- Exposição de logits para geração controlada
- Melhorias de desempenho e precisão em saídas estruturadas
- Aceleração por GPU para sampling
- Suporte a formatos adicionais além de JSON Schema
1 comentários
Opiniões no Hacker News
Se você precisa de restrições de saída mais fortes, o llama.cpp dá suporte a GBNF
https://github.com/ggerganov/llama.cpp/blob/master/grammars/...
Olhando de forma simples, como os modelos viram muito mais JSON, dá a impressão de que eles devem gerar JSON melhor do que outros formatos
Ótima notícia
Eu estava pensando em como montar um prompt geral para gerar dados CSV sem frases desnecessárias antes ou depois, como "Here is your data" ou "Please note blah blah"; agora é bom poder definir exatamente o formato de retorno desejado e passar a saída estruturada diretamente como CSV
Caso contrário, a saída pode ser tecnicamente em formato CSV, mas sem sentido. Pode ser que o modelo na verdade quisesse escrever uma resposta em parágrafos, e o amostrador de tokens acabou escolhendo tokens de baixa probabilidade que o modelo não queria muito dizer
\n e fazer parar empode evitar esse problemaFunciona. Coloquei a frase abaixo no gemma2:2b e obtive o JSON desejado
You have spent 190 at Fresh Mart. Current balance: 5098O resultado foi
{"amount": 190, "balance": 5098, "category": "Shopping", "place": "Fresh Mart"}Realmente impressionante. Era de fato uma das funcionalidades que eu queria
O bom do ollama é que ele dá a sensação de usar LLMs como outros programas UNIX, fazendo os LLMs parecerem naturalmente parte do UNIX
Só fico curioso para saber se alguém conseguiu rodar bem em GPU AMD. Ouvi dizer que é mais difícil, mas quero apoiar a concorrência quando comprar uma placa no ano que vem
Em um mini PC com 780M, aloquei 16 GB de memória compartilhada pela BIOS e rodou bem razoavelmente
Fico curioso sobre como esse tipo de restrição afeta a qualidade da saída de LLMs
Em alguns casos, se a qualidade da saída for maior, eu preferiria fazer parsing de Markdown ou texto comum
Na primeira, usa-se um modelo pesado e linguagem natural para lidar com o raciocínio em seções Markdown e fornecer a resposta final em linguagem natural. Quando possível, rotula-se claramente com cabeçalhos Markdown
Na segunda, um modelo mais barato e rápido transforma essa resposta em um formato de saída estruturada, para ser consumido pelas partes não LLM do pipeline
Basicamente, o modo de schema JSON cria limites limpos ao redor das partes ambíguas em linguagem natural, e o LLM atua como um pré-processador que captura a própria saída em um formato útil
Também é preciso informar o schema ao modelo. Se não informar, surgem mais problemas estranhos de tokenização
Por exemplo, se o schema espera a chave JSON
"foobarbaz"e a tokenização BPE padrão é["foobar", "baz"], as máscaras de tokens geradas pelas bibliotecas atuais de saída com restrições podem permitir que o modelo escolha entre"f","foo"e"foobar". Se o modelo escolher"foo", a restrição força os próximos tokens, por exemplo,"bar"e"baz". Assim, o modelo vê["foo", "bar", "baz"]em vez de["foobar", "baz"], o que o deixa confuso [0]Se o modelo souber pelo prompt que
"foobarbaz"é uma das chaves do schema, ele geralmente preferirá"foobar"a"foo"[0] Em modelos mais recentes, esses tokens são relacionados por causa da normalização, mas não são a mesma coisa
Depende muito de o LLM e o prompt já terem probabilidade de produzir uma resposta JSON. Quanto mais você força o LLM, menor a probabilidade de ele produzir uma entrada normal
Em modelos pequenos, chega-se mais rápido às bordas do espaço em que há poder preditivo significativo, e a saída começa a se aproximar de ruído aleatório
Não é uma medição rigorosa, é uma avaliação intuitiva depois de passar muito tempo em vários projetos com LLMs. Ainda não usei estas ferramentas específicas, mas o ollama já conseguia antes garantir saída JSON por um método que parecia semelhante, e também trabalhei com um parceiro em algo parecido com jsonformer para o oobabooga, outra ferramenta de runtime de LLM
Espero que, quando essas mudanças entrarem, também seja possível gerar estruturas de forma geral, não limitada apenas a JSON
É fácil queimar muitos tokens, mas, se o que você está tentando fazer justifica esse custo, dá para forçar bastante. Mesmo que não seja a qualidade absolutamente máxima, uma ferramenta que chega a 95% sem esforço já merece lugar na caixa de ferramentas
Fico curioso se dá para usar com qualquer modelo compatível
No meu hardware, só consigo rodar de forma estável modelos de 1B a 3B, por isso pergunto
Em modelos pequenos, os resultados podem variar, mas pedir “retorne x em JSON” costuma ajudar na precisão
O PR relacionado a esse recurso ficou aberto por quase 1 ano
É um pouco decepcionante que os mantenedores tenham ficado tão quietos
Espero que no ano que vem possamos acompanhar e mesclar melhor os PRs da comunidade
Ainda uso oobabooga. Graças ao suporte a exlv2, consigo fazer inferência de forma muito mais eficiente em duas 3090
Pelo que me lembro, embora o exl2 fosse mais rápido, especialmente em profundidades de bits mais baixas, a quantização gptq parecia melhor em termos de precisão
Tenho curiosidade sobre qual valor agregado isso oferece em comparação com
outlineshttps://www.souzatharsis.com/tamingLLMs/notebooks/structured...
Com base nas pesquisas recentes relacionadas a outlines/xgrammar, espero que possamos atualizar a amostragem para oferecer suporte a mais formatos, aumentar a precisão e melhorar o desempenho
Tenho curiosidade se existe uma melhor forma de fornecer entrada estruturada a um LLM
Por exemplo, no caso de inserir 100 frases e fazê-lo classificar cada uma de várias maneiras. Receber dados estruturados é fácil, mas meu método de colocar números de linha no começo parece meio tosco
Mas, nesse caso, o melhor é passar as frases uma por uma para não confundir o modelo
Se você montar o prompt como
"Classifique a seguinte frase. As regras são ..."+ a frase, dá para aproveitar o cache de prefixo, e o desempenho pode até ficar melhor do que fazer uma única consulta com tudo de uma vezClaro, isso só é possível quando há cache de prefixo e não há cobrança por token de entrada. Hoje em dia, a maioria dos provedores permite usar isso com custo menor se você indicar a intenção de usar cache de prefixo