11  Classificação de documentos usando OpenAI GPT-4o-mini

Neste notebook, vamos usar o modelo de linguagem GPT-4o-mini da OpenAI para classificar documentos em categorias. O GPT-4o-mini é um modelo de linguagem de última geração que pode ser usado para uma variedade de tarefas de processamento de linguagem natural, incluindo classificação de texto.

Começamos carregando o modelo GPT-4o-mini pré-treinado da OpenAI usando a biblioteca openai e, em seguida, usamos o modelo para classificar documentos em categorias. Para isso, fornecemos ao modelo um documento de entrada e ele nos dá uma lista de categorias possíveis para o documento.

Para acessar o modelo GPT-4o-mini, você precisará de uma chave de API da OpenAI. Esse notebook já disponibiliza uma chave de API para você usar, mas você também pode obter sua própria chave de API se desejar. Lembre-se que liberar a chave de API é uma prática insegura, então não estamos fazendo isso pelo fluxo ideal.

# chave liberada para uso até o final do semestre, com algumas restrições
import os
from dotenv import load_dotenv
# carregando a biblioteca da openai
from openai import OpenAI
from pydantic import BaseModel
import pandas as pd

# Carrega o arquivo .env
load_dotenv()

client = OpenAI()

# testando se a chave está funcionando
completion = client.chat.completions.create(
  model="gpt-4o-mini",
  messages=[
    {"role": "system", "content": "You are a helpful assistant."},
    {
      "role": "user",
      "content": "Escreva um cordel curto sobre a disciplina de Ciência de Dados no Direito."
    }
  ]
)

print(completion.choices[0].message.content)
**Cordel da Ciência de Dados no Direito**

No mundo virtual em que vivo,  
A ciência desponta e reluz,  
No Direito, uma nova prática,  
Transforma a lei, traz nova luz.  

Dados dançam, em trovas sutil,  
E o advogado, astuto e audaz,  
Com algoritmos faz o perfil,  
Decifra o futuro, implacável e eficaz.  

Na Justiça, as cifras ensinam,  
Padrões e tendências a explorar,  
Com análise e lógica divina,  
Sentenças justas a se firmar.  

Habeas data, é hora de ver,  
O sigilo e os dados a fluir,  
Com ética à frente a guiar,  
O Direito vem só a evoluir.  

Em estações de aprendizado,  
Estudamos o que há de novo,  
Na era digital, tão necessitado,  
A Ciência de Dados é o nosso povo.  

Por isso, advogados, atenção,  
A tecnologia não vai parar,  
Com dados em mãos, em colaboração,  
A Justiça se faz mais a brilhar.  

11.1 Controlando a temperatura

O modelo GPT-4o-mini tem um parâmetro chamado “temperatura” que controla a aleatoriedade das previsões do modelo. Quanto maior a temperatura, mais aleatórias as previsões do modelo serão. Vamos ver um exemplo com temperatura igual a 1, rodando duas vezes o mesmo pedido.

teste1 = client.chat.completions.create(
  model="gpt-4o-mini",
  messages=[
    {"role": "system", "content": "You are a helpful assistant."},
    {
      "role": "user",
      "content": "Qual o tesouro do jurista (ou seja, o diferencial quando comparado a um profissional da estatística, por exemplo) em pesquisas que envolvem ciência de dados no direito? Responda em uma frase."
    }
  ],
  temperature=1.0
)

print(teste1.choices[0].message.content)
O tesouro do jurista em pesquisas que envolvem ciência de dados no direito reside na sua expertise em interpretar e aplicar normas jurídicas, contextualizando os resultados estatísticos dentro do arcabouço legal e ético pertinente.

Agora, rodamos novamente o mesmo pedido. Veja que o resultado é diferente!

teste2 = client.chat.completions.create(
  model="gpt-4o-mini",
  messages=[
    {"role": "system", "content": "You are a helpful assistant."},
    {
      "role": "user",
      "content": "Qual o tesouro do jurista (ou seja, o diferencial quando comparado a um profissional da estatística, por exemplo) em pesquisas que envolvem ciência de dados no direito? Responda em uma frase."
    }
  ],
  temperature=1.0
)

print(teste2.choices[0].message.content)
O tesouro do jurista em pesquisas que envolvem ciência de dados no direito reside na sua capacidade de interpretar normas legais, contextos jurídicos e implicações éticas, além de aplicar conhecimentos jurídicos para analisar e contextualizar dados de forma crítica e fundamentada.

Agora, vamos rodar o mesmo pedido, mas com temperatura igual a 0, duas vezes. O que você acha que vai acontecer?

teste3 = client.chat.completions.create(
  model="gpt-4o-mini",
  messages=[
    {"role": "system", "content": "You are a helpful assistant."},
    {
      "role": "user",
      "content": "Qual o tesouro do jurista (ou seja, o diferencial quando comparado a um profissional da estatística, por exemplo) em pesquisas que envolvem ciência de dados no direito? Responda em uma frase."
    }
  ],
  temperature=0.0
)

print(teste3.choices[0].message.content)
O tesouro do jurista em pesquisas que envolvem ciência de dados no direito reside na sua capacidade de interpretar e contextualizar dados dentro do arcabouço legal e ético, garantindo que as análises respeitem os princípios jurídicos e os direitos fundamentais.
teste4 = client.chat.completions.create(
  model="gpt-4o-mini",
  messages=[
    {"role": "system", "content": "You are a helpful assistant."},
    {
      "role": "user",
      "content": "Qual o tesouro do jurista (ou seja, o diferencial quando comparado a um profissional da estatística, por exemplo) em pesquisas que envolvem ciência de dados no direito? Responda em uma frase."
    }
  ],
  temperature=0.0
)

print(teste4.choices[0].message.content)
O tesouro do jurista em pesquisas que envolvem ciência de dados no direito reside na sua capacidade de interpretar e contextualizar normas jurídicas, garantindo que a análise estatística seja aplicada de forma ética e relevante ao sistema legal.

11.2 Desenvolvendo prompts para anotação de documentos jurídicos

A anotação automática de documentos jurídicos utilizando modelos de linguagem aumenta significativamente a eficiência na anotação de documentos. No entanto, a qualidade dos resultados pode variar dependendo da formulação do prompt utilizado.

Trata-se de uma área em desenvolvimento, então ainda não temos uma resposta final sobre como formular os prompts. No entanto, algumas dicas podem ser úteis:

  1. Escreva instruções claras e objetivas: O prompt deve ser o mais claro e específico possível. Experimente verificar com você ou outra pessoa se as instruções são compreensíveis e diretas.

  2. Deixe claro quem é o assistente e qual seu papel: O modelo de linguagem é um assistente, e é importante deixar claro qual é o papel dele na tarefa. Por exemplo, “você é um assistente jurídico que realiza a análise de petições iniciais em processos cíveis, resumindo as informações mais importantes para posterior avaliação de profissionais da advocacia”.

  3. Descreva o que o modelo deve esperar de entrada: Sempre que necessário, forneça contexto relevante ao modelo. Isso pode incluir a descrição do tipo de documento, o objetivo da anotação e as características específicas do caso em questão. Por exemplo, uma petição inicial de um processo é diferente de um tweet.

  4. Especifique o formato de saída: Indicar o formato exato da resposta esperada é uma boa prática. Geralmente, pediremos para que a resposta seja em um JSON, e temos um parâmetro para forçar esse comportamento no modelo.

  5. Inclua exemplos: Fornecer exemplos no prompt ajuda o modelo a entender o formato e o conteúdo esperado na resposta. O nome técnico disso é “one shot” ou “few shotlearning.

  6. Teste com alguns casos: Sempre verifique se o prompt está funcionando com alguns casos antes de rodar para uma amostra grande, e faça ajustes caso necessário.

  7. Pedir um resumo: Para tarefas complexas, pode ser útil pedir para o modelo de linguagem resumir a resposta em um parágrafo ou algumas frases, antes de realizar uma anotação em categorias. Isso pode aumentar a qualidade da resposta final.

Obs: você pode usar o ChatGPT para ajudar na criação de prompts. Isso economiza tempo e cria prompts eficazes.

prompt_ruim = """
Classifique o documento abaixo nessas variáveis:

- faz parte do escopo?
- quais drogas estão envolvidas?
- quantidade de maconha em gramas
- quantidade de cocaína em gramas
- quantidade de crack em gramas
- sexo da pessoa acusada
- reincidente
- decisão
- tipo de pena
- tempo da pena
"""
prompt_otimizado = """
Você é um assistente de inteligência artificial que auxilia na anotação de sentenças judiciais do Tribunal de Justiça de São Paulo em processos envolvendo tráfico de drogas.

Você receberá o texto da sentença e deverá retornar um arquivo JSON, seguindo as regras abaixo:

- O processo faz parte do escopo da pesquisa? O caso deve: Ser um caso relacionado a tráfico de drogas; Envolver porte de maconha, crack ou cocaína; Envolver apenas uma pessoa acusada.
- Quantidade de maconha em gramas: Preencha apenas os números. Coloque 0 se o caso não envolve essa droga. Use "," como separador decimal. Se a decisão não menciona a quantidade em gramas, faça a conversão, seguindo a regra: 1 porção = 2 gramas.
- Quantidade de cocaína em gramas: Preencha apenas os números. Coloque 0 se o caso não envolve essa droga. Use "," como separador decimal. Se a decisão não menciona a quantidade em gramas, faça a conversão, seguindo a regra: 1 porção ou pino = 0,5 grama.
- Quantidade de crack em gramas: Preencha apenas os números. Coloque 0 se o caso não envolve essa droga. Use "," como separador decimal. Se a decisão não menciona a quantidade em gramas, faça a conversão, seguindo a regra: 1 porção = 0,1 grama.
- Decisão: pode ser procedente (condenação), improcedente / punibilidade extinta, ou parcialmente procedente / advertência.
- Tipo de pena: pode ser fechado, semiaberto ou aberto.
- Tempo da pena (em meses): Preencha apenas os números. Converta o tempo em meses. Por exemplo, 2 anos e 7 meses = 31 meses.

Retorne um arquivo JSON com as seguintes informações:

{
 "escopo": "sim/não",
 "maconha": "sim/não",
 "cocaina": "sim/não",
 "crack": "sim/não",
 "qtd_maconha": 0,
 "qtd_cocaina": 0,
 "qtd_crack": 0,
 "sexo": "masculino/feminino/não informado",
 "reincidente": "sim/não/não informado",
 "decisao": "procedente/improcedente/parcialmente procedente",
 "tipo_pena": "fechado/semiaberto/aberto",
 "tempo": 0
}
"""

11.2.1 Aplicando prompt em um caso

drogas = pd.read_csv("https://github.com/jtrecenti/main-cdad2/releases/download/data/drogas.csv")

drogas.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 991 entries, 0 to 990
Data columns (total 14 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   file              991 non-null    object 
 1   processo          991 non-null    object 
 2   pagina            991 non-null    int64  
 3   hora_coleta       0 non-null      float64
 4   duplicado         991 non-null    bool   
 5   classe            990 non-null    object 
 6   assunto           991 non-null    object 
 7   magistrado        991 non-null    object 
 8   comarca           991 non-null    object 
 9   foro              991 non-null    object 
 10  vara              991 non-null    object 
 11  disponibilizacao  991 non-null    object 
 12  julgado           991 non-null    object 
 13  cd_doc            991 non-null    object 
dtypes: bool(1), float64(1), int64(1), object(11)
memory usage: 101.7+ KB
processo = '15005860220238260569'

julgado = drogas[drogas['processo']==processo]['julgado'].iloc[0]

print(julgado[:2000])
TRIBUNAL DE JUSTIÇA DO ESTADO DE SÃO PAULO COMARCA de Boituva Foro de Boituva 1ª Vara Rua Manoel dos Santos Freire, 161, Centro - CEP 18550-000, Fone: (15) 3263-2120, Boituva-SP - E-mail: boituva1@tjsp.jus.br 1500586-02.2023.8.26.0569 - lauda SENTENÇA Processo nº: 1500586-02.2023.8.26.0569 Classe - Assunto Ação Penal - Procedimento Ordinário - Tráfico de Drogas e Condutas Afins Autor: Justiça Pública Réu: ROSANA LEMES DE SOUZA Juiz(a) de Direito: Dr(a). Liliana Regina de Araujo Heidorn Abdala Vistos. ROSANA LEMES DE SOUZA foi denunciada como incursa no artigo 33, caput, c/c art. 40, inciso III, ambos da Lei 11.343/06, porque no dia 01 de julho de 2023, aproximadamente às 08h00min, na Estrada Tatuí/Iperó, Área Rural, Estabelecimento Prisional, na cidade de Iperó e comarca de Boituva, ROSANA LEMES DE SOUZA, qualificada a fls. 14 e indiciada a fls. 62, adquiriu e trazia consigo, para fornecer e entregar ao consumo de terceiros, 98,13g (noventa e oito gramas e treze centigramas) líquidos de maconha, acondicionados em 1 (uma) porção, conforme laudo pericial de fls. 31/33 e 106/108, substância entorpecente que determina dependência física e psíquica, sem autorização legal e regulamentar para tanto. Segundo o apurado, a denunciada estava no estabelecimento prisional de Iperó para realização de visitas, quando foi submetida à revista. Ao verificar o que tinha no inteiro de um pacote que a denunciada trazia consigo, foi localizada 1 (uma) porção de maconha no interior de 1 (um) absorvente. As circunstâncias em que se deram o flagrante, evidenciam a prática do tráfico de entorpecentes. Presa em flagrante, a ré foi beneficiada com a liberdade provisória mediante o cumprimento das medidas cautelares previstas no artigo 319, do C.P.P. (fl.89/91). Regularmente notificada (fl. 158), a ré apresentou defesa preliminar (fls.141/144). A denúncia foi recebida (fl. 150). Regularmente citada, foi realizada a instrução processual, com oitiva de duas testemunhas em comum e o interrogatório
print(julgado[-2000:])
prisional, nos termos do artigo 40, inciso III, da Lei 11.343/2006, fixando-a definitivamente em 01 (um) ano, 11 (onze) meses e 10 (dez) dias, e 193 (cento e noventa e três) dias-multa. Vislumbro que a acusada faz jus ao benefício previsto nos artigos 43 e nos seguintes do Código Penal, substituo a pena privativa de liberdade em prestação de serviço à comunidade e 10 dias multa, nos termos do artigo 44, § 2º, do Código Penal, que será executada perante o Juízo das Execuções Criminais. Em caso de descumprimento, fixo o regime aberto para o início do cumprimento da pena. Pelo exposto JULGO PROCEDENTE a presente pretensão punitiva, e o faço para CONDENAR a ré ROSANA LEMES DE SOUZA à pena de 01 (um) ano, 11 (onze) meses e 10 (dez) dias, e 193 (cento e noventa e três) dias-multa, substituída por prestação de serviço à comunidade, na forma acima disposta, por infração ao artigo 33, caput, c/c art. 40, inciso III, ambos da Lei 11.343/06. Defiro recurso em liberdade. São parcas as informações acerca da situação financeira da ré, por isso arbitro o valor do dia-multa no seu mínimo legal, calculado sobre o salário mínimo da data dos fatos e atualizado até o dia do efetivo pagamento. Nos termos do artigo 32, parágrafos 1º e 2º, da Lei 11.343/06, AUTORIZO a incineração dos entorpecentes apreendidos, se ainda não providenciado. Condeno a acusada ao pagamento das custas equivalentes a 100 UFESP's, nos termos do artigo 4º, inciso III, item 5, § 9º, alínea “a” da Lei nº 11.608, de 29 de dezembro de 2003, obrigação que fica suspensa em atenção ao disposto nos artigos 11 e 12 da Lei nº 1.060/50, posto que beneficiários da justiça gratuita, pois defendidos por força do convênio Defensoria Pública/OAB. Arbitro os honorários advocatícios no valor máximo previsto em convênio. Expeça-se a devida certidão. Publicada em audiência. Saem todos intimados. Cumpra-se. Boituva, 19 de agosto de 2024. DOCUMENTO ASSINADO DIGITALMENTE NOS TERMOS DA LEI 11.419/2006, CONFORME IMPRESSÃO À MARGEM DIREITA
def classificar_decisao (prompt, julgado):
  completion = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
      {"role": "system", "content": prompt},
      {"role": "user", "content": julgado}
    ],
    temperature=0.0
  )
  return completion.choices[0].message.content
res_ruim = classificar_decisao(prompt_ruim, julgado)

print(res_ruim)
- faz parte do escopo? Sim
- quais drogas estão envolvidas? Maconha
- quantidade de maconha em gramas: 98,13g
- quantidade de cocaína em gramas: 0g
- quantidade de crack em gramas: 0g
- sexo da pessoa acusada: Feminino
- reincidente: Não
- decisão: Condenação
- tipo de pena: Prestação de serviço à comunidade
- tempo da pena: 1 ano, 11 meses e 10 dias
res_otimizado = classificar_decisao(prompt_otimizado, julgado)

print(res_otimizado)
```json
{
  "escopo": "sim",
  "maconha": "sim",
  "cocaina": "não",
  "crack": "não",
  "qtd_maconha": 98,
  "qtd_cocaina": 0,
  "qtd_crack": 0,
  "sexo": "feminino",
  "reincidente": "não",
  "decisao": "procedente",
  "tipo_pena": "aberto",
  "tempo": 23
}
```

11.3 Exercício:

Teste o prompt otimizado para novos processos e compare com as classificações manuais

processo = '<processo aqui>'
julgado = drogas[drogas['processo']==processo]['julgado'].iloc[0]

classificar_decisao(prompt_otimizado, julgado)

11.4 Aula 05 até aqui


11.5 Aula 06

Dá para melhorar um pouco mais ainda. Vamos forçar o resultado a um formato específico, que podemos transformar em um DataFrame python diretamente.

class JsonSchema(BaseModel):
  escopo: str
  maconha: str
  cocaina: str
  crack: str
  qtd_maconha: float
  qtd_cocaina: float
  qtd_crack: float
  sexo: str
  reincidente: str
  decisao: str
  tipo_pena: str
  tempo: float

def classificar_decisao_df (prompt, julgado):
  completion = client.beta.chat.completions.parse(
    model="gpt-4o-mini",
    messages=[
      {"role": "system", "content": prompt},
      {"role": "user", "content": julgado}
    ],
    temperature=0.0,
    response_format = JsonSchema
  )
  return completion.choices[0].message.parsed.dict()
res_otimizado_df = classificar_decisao_df(prompt_otimizado, julgado)

print(res_otimizado_df)
{'escopo': 'sim', 'maconha': 'sim', 'cocaina': 'não', 'crack': 'não', 'qtd_maconha': 98.0, 'qtd_cocaina': 0.0, 'qtd_crack': 0.0, 'sexo': 'feminino', 'reincidente': 'não', 'decisao': 'procedente', 'tipo_pena': 'aberto', 'tempo': 22.0}

É possível converter para um DataFrame diretamente, veja:

pd.DataFrame([res_otimizado_df])
escopo maconha cocaina crack qtd_maconha qtd_cocaina qtd_crack sexo reincidente decisao tipo_pena tempo
0 sim sim não não 98.0 0.0 0.0 feminino não procedente aberto 22.0

Agora, podemos fazer isso para vários casos, com um laço:

casos = [
  '00029796520178260542',
  '15289122520238260228',
  '15071822120248260228',
  '15003527020248260347',
  '15027831120238260542',
  '15010156220248260559'
]

resultados = []

for caso in casos:
  print(caso)
  txt_caso = drogas[drogas['processo'] == caso]['julgado'].iloc[0]
  res = classificar_decisao_df(prompt_otimizado, txt_caso)
  resultados.append(res)

df_resultados = pd.DataFrame(resultados)
00029796520178260542
15289122520238260228
15071822120248260228
15003527020248260347
15027831120238260542
15010156220248260559
df_resultados
escopo maconha cocaina crack qtd_maconha qtd_cocaina qtd_crack sexo reincidente decisao tipo_pena tempo
0 sim sim sim não 63.0 7.0 0.0 masculino sim parcialmente procedente fechado 84.0
1 sim sim sim não 3.0 11.0 0.0 masculino não procedente fechado 32.0
2 sim sim sim não 51.4 18.0 0.0 masculino não procedente aberto 20.0
3 sim não sim não 0.0 13.0 0.0 masculino não procedente aberto 8.0
4 sim sim sim sim 6.0 9.0 4.0 masculino não procedente aberto 20.0
5 sim sim sim não 37.0 12.0 0.0 masculino não parcialmente procedente aberto 32.0

Finalmente, podemos salvar o resultado em um arquivo CSV:

df_resultados.to_csv("resultados.csv", index = False)