2 pontos por GN⁺ 2024-12-09 | 1 comentários | Compartilhar no WhatsApp
  • É 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

 
GN⁺ 2024-12-09
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/...

    • Parece que é exatamente isso que estão usando
    • Fico curioso para saber se a qualidade da saída para gramáticas arbitrárias foi satisfatória
      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
    • Fico curioso sobre em que sentido isso é mais poderoso
  • Ó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

    • Ainda assim, é preciso incluir uma instrução para gerar CSV para que o prompt entre no contexto correto
      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
    • Em muitos casos, pré-preencher o início da saída com \n e fazer parar em pode evitar esse problema
  • Funciona. Coloquei a frase abaixo no gemma2:2b e obtive o JSON desejado
    You have spent 190 at Fresh Mart. Current balance: 5098
    O resultado foi {"amount": 190, "balance": 5098, "category": "Shopping", "place": "Fresh Mart"}

    • É um JSON bastante inconsistente, mas sendo um modelo muito pequeno e ainda por cima gemma, dá para entender
  • 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

    • Dá para fazer. Funciona até em GPU integrada
      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

    • Ao usar modelos da OpenAI, se você puder bancar o custo extra de tokens, uma estratégia de duas passagens funcionou muito bem
      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
    • Depende de quanto o modelo foi ajustado para saída JSON
      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
    • Varia conforme a situação, mas, do ponto de vista de inferência, há impacto negativo, e na maioria dos casos a diferença não foi tão grande
      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
    • Estou acompanhando pesquisas relacionadas. Estou analisando formas de melhorar a amostragem em geral, tanto em velocidade quanto em precisão
      Espero que, quando essas mudanças entrarem, também seja possível gerar estruturas de forma geral, não limitada apenas a JSON
    • Posso dizer que eu estava completamente errado sobre a utilidade de ferramentas como o instructor
      É 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

    • Sou o autor do post do blog. Deve ser possível usar qualquer modelo
      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

    • Sou o autor do texto e um dos mantenedores. Concordo. Os mantenedores demoraram a responder e, de modo geral, quero incentivar mais contribuições
      Espero que no ano que vem possamos acompanhar e mesclar melhor os PRs da comunidade
    • Pelo clima, parece que estão seguindo um caminho centrado na empresa, então veem tudo por essa lente e pela perspectiva de maximização de receita
    • Eu também queria colaborar na construção disso, mas a conversa foi interrompida de repente
  • Ainda uso oobabooga. Graças ao suporte a exlv2, consigo fazer inferência de forma muito mais eficiente em duas 3090

    • Faz tempo que não mexo no ooba, mas tenho curiosidade sobre como está a situação do exl2 e de métodos de quantização não homogêneos como q3k_s
      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 outlines
    https://www.souzatharsis.com/tamingLLMs/notebooks/structured...

    • Sou o autor do blog. A implementação atual pôde ser feita rapidamente usando llama.cpp GBNF. O maior valor agregado neste momento é o fato de termos lançado a funcionalidade
      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

    • Os modelos foram treinados em Markdown, JSON e várias linguagens de programação, então qualquer um desses formatos deve funcionar
      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 vez
      Claro, 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