diff --git a/.gitignore b/.gitignore index 6769e21..cc60a92 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +temp/* +.env +outputs/* + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/README.md b/README.md index aabed28..3ac6b82 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ Uses [ollama](https://github.com/ollama/ollama-python) to run LLMs locally. 1. (Not implemented) Set grading style in `main.py`. - "binary": is either right or wrong - "qualitative": ask claude -1. Run `python eval.py`. -1. Logs from the run will be in `output/.yaml`. +1. Run `python main.py`. +1. Logs from the run will be in `outputs/.yaml`. ## Parameters diff --git a/eval_models.py b/eval_models.py index f7eabd3..9170e6e 100644 --- a/eval_models.py +++ b/eval_models.py @@ -1,7 +1,8 @@ from utils.my_types import Model models: list[Model] = [ - Model("llama3"), + # Model("llama3"), + Model("gpt-4o"), # Model("phi3"), # Model("mistral"), ] diff --git a/eval_prompts.py b/eval_prompts.py index 3e438fa..5471253 100644 --- a/eval_prompts.py +++ b/eval_prompts.py @@ -2,37 +2,51 @@ prompts: list[Prompt] = [ Prompt( - id="menciona_0", + id="stj1", slots="{variable}", - content="""Sua tarefa é analisar e responder se: - a. o texto a seguir menciona a necessidade de comprar remédios ou itens de saúde; ou - b. o texto menciona um evento imprevisto e excepcional que tenha levado à busca de benefício; ou - c. nenhum dos dois. - Aqui está o texto: - ### - {variable} - ### - Primeiro, analise cuidadosamente o texto em um rascunho. Depois, responda "<> (INDEFERIDO - SAÚDE)", "<> (INDEFERIDO - EVENTO)" ou "<> (DEFERIDO)".""", + content="""Responda as seguintes perguntas sobre o texto a seguir. Responda em formato JSONL. Caso a resposta envolva nomes, use apenas os primeiros nomes dos ministros. Caso não haja dados para necesários para responder, responda com "null". + +Perguntas: + +1. qual foi o resultado da decisão tomada? responda com "ACEITO", "PARCIAL" ou "NEGADO" +2. a decisão foi tomada em unanimidade? responda com "SIM" OU "NAO" +3. houve modificativos? responda "SIM" ou "NAO" +4. houve conhecer? responda com "SIM", "PARCIAL" ou "NAO" +5. qual ministro presidiu o julgamento? +6. houve algum ministro ausente? +7. quais ministros votaram a favor? +8. algum ministro foi vencido? +9. algum ministro lavrará o acórdão? +10. de quem foi o voto de desempate? +11. quais ministros fizeram voto-vista? + + +Exemplo de formato da resposta: + +{"resultado": "ACEITO","unanimidade": "SIM","modificativos": "NAO","conhecer": "PARCIAL","presidiu": "Paulo","ausente": null,"aFavor": "Benedito, Sérgio, Regina, Gurgel","vencidos": null,"lavrara": null,"desempate": null,"votoVista": "Regina"} + + +Aqui está o texto: +### +{variable} +### +""", ), - # Prompt( - # id="deferimento", - # slots="{variable}", - # content="""Você irá analisar uma solicitação de benefício e determinar se ela deve ser deferida ou indeferida com base nas regras fornecidas. - # Aqui estão as regras para determinar a elegibilidade: - # ### - # 1. O benefício não pode ser deferido para a compra de remédios, insumos de saúde, órteses ou próteses. - # 2. O benefício não pode ser deferido se não houver um fato novo e excepcional relatado no parecer. - # ### - # Aqui está a solicitação a ser analisada: - # ### - # {variable} - # ### - # Primeiro, analise cuidadosamente a solicitação e as regras em um . Considere se a solicitação menciona a necessidade de comprar remédios ou itens de saúde (o que levaria a um indeferimento pela regra 1) e se ela relata um evento imprevisto e excepcional que desorganizou a família (necessário para um deferimento pela regra 2). - # Depois de analisar, forneça sua resposta final em uma tag . Responda: - # "A" se o parecer não contém um evento imprevisto que desorganizou a família - # "B" se o benefício não pode ser utilizado para adquirir medicamentos, insumos de saúde, órteses ou - # próteses - # "C" se o parecer contém um evento inesperado que desorganizou a família - # Inclua uma breve justificativa junto com a letra da resposta.""", - # ), ] + + +# @dataclass +# class Decisao: +# id: str +# decisao: str +# unanimidade: bool +# modificativos: bool +# resultado: str # "aceito" | "parcial" | "negado" +# conhecer: Optional[str] # "sim" | "parcial" | "nao" | NaN +# aFavor: Optional[str] # ministros +# vencidos: Optional[str] # ministros +# presidiu: Optional[str] # ministros +# ausente: Optional[str] # ministros +# lavrara: Optional[str] # ministros +# desempate: Optional[str] # ministros +# votoVista: Optional[str] # ministros diff --git a/eval_variables.py b/eval_variables.py index 0e3a22c..55dcde8 100644 --- a/eval_variables.py +++ b/eval_variables.py @@ -1,42 +1,40 @@ from utils.my_types import Variable -options = ["<>", "<>", "<>"] +options = [] variables: list[Variable] = [ Variable( - id="insegurança_alimentar", - content="A família é composta pela mãe e os filhos Sofia (3) e Davi (5). Não possuem fonte de renda. Solicito o auxílio vulnerabilidade devido a situação de insegurança alimentar da família.", - expected=["<>"], - options=options, - ), - Variable( - id="fralda_geriátrica", - content="Família residente em domicílio improvisado e tem um idoso acamado. Solicita-se o auxílio vulnerabilidade a fim de garantir a compra de fraldas geriátricas e outros insumos.", - expected=["<>"], - options=options, - ), - Variable( - id="acidente", - content="Família monoparental composta por Maria e os filhos João (12) e Marcos (10). Tinham como fonte de renda o trabalho informal de Maria, mas esta sofreu um acidente e está impossibilidade de trabalhar. Solicita-se, portanto, o auxílio vulnerabilidade.", - expected=["<>"], - options=options, - ), - Variable( - id="remédios", - content="A família está com o aluguel atrasado e necessita da compra de remédios para José (48), que tem lúpus. Solicita-se, portanto, o auxílio vulnerabilidade.", - expected=["<>"], - options=options, - ), - Variable( - id="pensão", - content="Família composta por Jaqueline e a filha Helena (2). Tinham como fonte de renda a pensão alimentícia de Helena. No entanto, o progenitor deixou de realizar o pagamento há dois meses. Solicita-se, portanto, o auxílio vulnerabilidade, a fim de mitigar as desproteções vividas pela família.", - expected=["<>"], - options=options, - ), - Variable( - id="despesas_essenciais", - content="Família monoparental composta por Josefa e 5 filhos com idades entre 1 e 17 anos. Contam apenas com a renda de coleta de material reciclável e relatam dificuldade para manter as despesas essenciais. Solicita-se, portanto, o auxílio vulnerabilidade.", - expected=["<>"], + id="recurso negado", + content="Vistos e relatados estes autos em que são partes as acima indicadas, acordam os Ministros da PRIMEIRA TURMA do Superior Tribunal de Justiça, em sessão virtual de 03/10/2023 a 09/10/2023, por unanimidade, negar provimento ao recurso, nos termos do voto do Sr. Ministro Relator. Os Srs. Ministros Benedito Gonçalves, Sérgio Kukina, Regina Helena Costa e Gurgel de Faria votaram com o Sr. Ministro Relator. Presidiu o julgamento o Sr. Ministro Paulo Sérgio Domingues.", + expected=[ + """```jsonl\n\n{"resultado": "NEGADO","unanimidade": "SIM","modificativos": "NAO","conhecer": null,"presidiu": "Paulo","ausente": null,"aFavor": "Benedito, Sérgio, Regina, Gurgel","vencidos": null,"lavrara": null,"desempate": null,"votoVista": null}\n\n```""" + ], options=options, ), + # Variable(id="recurso parcial"), + # Variable(id="recurso aceito"), + # Variable(id="agravo negado"), + # Variable(id="agravo parcial"), + # Variable(id="agravo aceito"), + # Variable(id="x negado"), + # Variable(id="x parcial"), + # Variable(id="x aceito"), + # Variable(id="maioria"), + # Variable(id="modificativos"), + # Variable(id="conhecer nan"), + # Variable(id="conhecer"), + # Variable( + # id="conhecer parcial", + # content="Vistos e relatados estes autos em que são partes as acima indicadas, acordam os Ministros da PRIMEIRA TURMA do Superior Tribunal de Justiça, em sessão virtual de 26/09/2023 a 02/10/2023, por unanimidade, conhecer parcialmente do recurso, mas lhe negar provimento, nos termos do voto do Sr. Ministro Gurgel de Faria. Os Srs. Ministros Benedito Gonçalves, Sérgio Kukina, Regina Helena Costa e Paulo Sérgio Domingues votaram com o Sr. Ministro Relator. Presidiu o julgamento o Sr. Ministro Paulo Sérgio Domingues.", + # expected=[ + # """{"resultado": "NEGADO","unanimidade": "SIM","modificativos": "NAO","conhecer": "PARCIAL","presidiu": "Paulo","ausente": null,"aFavor": Benedito, Sérgio, Regina, Paulo","vencidos": null,"lavrara": null,"desempate": null,"votoVista": null}""" + # ], + # options=options, + # ), + # Variable(id="nao conhecer"), + # Variable(id="ausente"), + # Variable(id="lavrara"), + # Variable(id="desempate"), + # Variable(id="voto vista 1"), + # Variable(id="voto vista 2"), ] diff --git a/main.py b/main.py index 84c929b..d993b92 100644 --- a/main.py +++ b/main.py @@ -21,6 +21,7 @@ VARIABLES = variables TEMPERATURE = 0.0 GRADING_TYPE = "binary" +DANGER_MODE = True # does not ask permission about prices. use with care. def main(): @@ -67,8 +68,7 @@ def main(): ) try: - output = query(model, prompt, variable, TEMPERATURE) - response = output["message"]["content"] # type: ignore + response = query(model, prompt, variable, TEMPERATURE, DANGER_MODE) grade = grade_response(response, variable, GRADING_TYPE) result.response = response diff --git a/requirements.txt b/requirements.txt index 33e7563..f73207a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,188 @@ +absl-py==2.1.0 anyio==4.3.0 +argon2-cffi==23.1.0 +argon2-cffi-bindings==21.2.0 +arrow==1.3.0 +asttokens==2.4.1 +astunparse==1.6.3 +async-lru==2.0.4 +attrs==23.2.0 +autopep8==2.0.4 +Babel==2.14.0 +beautifulsoup4==4.12.3 +bleach==6.1.0 +blinker==1.4 certifi==2024.2.2 -exceptiongroup==1.2.1 +cffi==1.16.0 +charset-normalizer==3.3.2 +check50==3.3.11 +click==8.1.7 +comm==0.2.2 +command-not-found==0.3 +cryptography==3.4.8 +dbus-python==1.2.18 +debugpy==1.8.1 +decorator==5.1.1 +defusedxml==0.7.1 +distro==1.7.0 +distro-info==1.1+ubuntu0.1 +EditorConfig==0.12.4 +exceptiongroup==1.2.0 +executing==2.0.1 +fastjsonschema==2.19.1 +filelock==3.13.4 +flatbuffers==24.3.25 +fqdn==1.5.1 +fsspec==2024.3.1 +gast==0.5.4 +google-pasta==0.2.0 +greenlet==1.1.2 +grpcio==1.62.2 h11==0.14.0 +h5py==3.11.0 httpcore==1.0.5 +httplib2==0.20.2 httpx==0.27.0 -idna==3.7 -ollama==0.1.9 +huggingface-hub==0.22.2 +icdiff==2.0.7 +idna==3.6 +importlib-metadata==4.6.4 +iniconfig==2.0.0 +ipykernel==6.29.4 +ipython==8.23.0 +isoduration==20.11.0 +jedi==0.19.1 +jeepney==0.7.1 +jellyfish==0.11.2 +Jinja2==3.1.3 +joblib==1.4.0 +jsbeautifier==1.15.1 +json5==0.9.25 +jsonpointer==2.4 +jsonschema==4.21.1 +jsonschema-specifications==2023.12.1 +jupyter-events==0.10.0 +jupyter-lsp==2.2.5 +jupyter_client==8.6.1 +jupyter_core==5.7.2 +jupyter_server==2.14.0 +jupyter_server_terminals==0.5.3 +jupyterlab==4.1.6 +jupyterlab_pygments==0.3.0 +jupyterlab_server==2.27.0 +kaggle==1.6.12 +keras==3.3.2 +keyring==23.5.0 +launchpadlib==1.10.16 +lazr.restfulclient==0.14.4 +lazr.uri==1.0.6 +lib50==3.0.11 +libclang==18.1.1 +Markdown==3.6 +markdown-it-py==3.0.0 +MarkupSafe==2.1.5 +matplotlib-inline==0.1.7 +mdurl==0.1.2 +mistune==3.0.2 +ml-dtypes==0.3.2 +more-itertools==8.10.0 +msgpack==1.0.3 +namex==0.0.8 +nbclient==0.10.0 +nbconvert==7.16.3 +nbformat==5.10.4 +nest-asyncio==1.6.0 +netifaces==0.11.0 +nltk==3.8.1 +notebook==7.1.3 +notebook_shim==0.2.4 +numpy==1.26.4 +oauthlib==3.2.0 +ollama==0.2.1 +opt-einsum==3.3.0 +optree==0.11.0 +overrides==7.7.0 +packaging==24.0 +pandas==2.2.2 +pandocfilters==1.5.1 +parso==0.8.4 +pexpect==4.9.0 +pillow==10.3.0 +platformdirs==4.2.0 +pluggy==1.4.0 +polars==0.20.22 +prometheus_client==0.20.0 +prompt-toolkit==3.0.43 +protobuf==4.25.3 +psutil==5.9.8 +ptyprocess==0.7.0 +pure-eval==0.2.2 +pycodestyle==2.10.0 +pycparser==2.22 +pygame==2.5.2 +Pygments==2.17.2 +PyGObject==3.42.1 +PyJWT==2.3.0 +pynvim==0.4.2 +pyparsing==2.4.7 +pytesseract==0.3.10 +python-apt==2.4.0+ubuntu2 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.1 +python-json-logger==2.0.7 +python-magic==0.4.27 +python-slugify==8.0.4 +pytz==2024.1 PyYAML==6.0.1 +pyzmq==26.0.2 +referencing==0.34.0 +regex==2024.4.16 +requests==2.31.0 +rfc3339-validator==0.1.4 +rfc3986-validator==0.1.1 +rich==13.7.1 +rpds-py==0.18.0 +safetensors==0.4.3 +scikit-learn==1.4.2 +scipy==1.13.0 +SecretStorage==3.3.1 +Send2Trash==1.8.3 +six==1.16.0 sniffio==1.3.1 +soupsieve==2.5 +stack-data==0.6.3 +style50==2.10.2 +submit50==3.1.4 +systemd-python==234 +tensorboard==2.16.2 +tensorboard-data-server==0.7.2 +tensorflow==2.16.1 +tensorflow-io-gcs-filesystem==0.36.0 +termcolor==1.1.0 +terminado==0.18.1 +text-unidecode==1.3 +tf_keras==2.16.0 +threadpoolctl==3.4.0 +tinycss2==1.2.1 +tokenizers==0.19.1 +tomli==2.0.1 +tornado==6.4 +tqdm==4.66.2 +traitlets==5.14.3 +transformers==4.40.1 +types-python-dateutil==2.9.0.20240316 typing_extensions==4.11.0 +tzdata==2024.1 +ubuntu-advantage-tools==8001 +ufw==0.36.1 +unattended-upgrades==0.1 +uri-template==1.3.0 +urllib3==2.2.1 +wadllib==1.3.6 +wcwidth==0.2.13 +webcolors==1.13 +webencodings==0.5.1 +websocket-client==1.7.0 +Werkzeug==3.0.2 +wrapt==1.16.0 +zipp==1.0.0 diff --git a/utils/llm.py b/utils/llm.py index bf3163e..1f016cc 100644 --- a/utils/llm.py +++ b/utils/llm.py @@ -1,16 +1,51 @@ -from typing import Iterable +from openai import OpenAI +import sys from utils.my_types import Model, Prompt, Variable import ollama +from dotenv import load_dotenv +from utils.openai import ( + estimate_costs, + get_n_tokens, + get_costs_gpt4o, + ask_permission, +) + def query( - model: Model, prompt: Prompt, variable: Variable, temp: float -) -> Iterable[object]: - response = ollama.chat( - model=model.id, - messages=build_messages(prompt, variable), # type: ignore - options={"temperature": temp}, - ) - return response + model: Model, prompt: Prompt, variable: Variable, temp: float, danger_mode: bool +) -> str: + + if model.id != "gpt-4o": + response = ollama.chat( + model=model.id, + messages=build_messages(prompt, variable), # type: ignore + options={"temperature": temp}, + ) + return response["message"]["content"] # type: ignore + + if model.id == "gpt-4o": + + messages = build_messages(prompt, variable) + n_tokens = get_n_tokens(model.id, str(messages)) + estimate_costs(model.id, n_tokens) + + if not danger_mode: + if not ask_permission(): + sys.exit(0) + + load_dotenv() + client = OpenAI() + response = client.chat.completions.create( + model=model.id, + messages=messages, # type: ignore + temperature=temp, + ) + + get_costs_gpt4o(response) + + return response.choices[0].message.content # type: ignore + + raise ValueError("Invalid model type") def build_messages(prompt: Prompt, variable: Variable) -> list[dict[str, str]]: diff --git a/utils/openai.py b/utils/openai.py new file mode 100644 index 0000000..0661e9e --- /dev/null +++ b/utils/openai.py @@ -0,0 +1,83 @@ +import tiktoken + +OMNI_INPUT = 5 +OMNI_OUTPUT = 15 + + +# @retry(tries=10, delay=1, backoff=2) +# def query_gpt(model: str, prompt: str, temp: float) -> str: +# client = OpenAI() + +# message = client.chat.completions.create( +# model=model, +# temperature=temp, +# messages=prompt, # type: ignore +# ) +# return message + + +# def get_prompt(prompt, snippet: str) -> str: +# return prompt.replace("{snippet}", snippet) + + +# def get_messages(prompt: str): +# messages = [] +# message = {"role": "user", "content": prompt} +# messages.append(message) +# return messages + + +def estimate_costs(model: str, n_tokens: int) -> None: + if model == "gpt-4o": + MODEL_INPUT = OMNI_INPUT / 1_000_000 + MODEL_OUTPUT = OMNI_OUTPUT / 1_000_000 + else: + raise ValueError("Model not supported.") + + input_cost = n_tokens * MODEL_INPUT + output_cost = n_tokens * MODEL_OUTPUT + total_cost = input_cost + output_cost + + print("Antiquarian AI Cleanup Cost Estimation:") + print("Model: ", model) + print(f"Tokens: {n_tokens} + {n_tokens}") + print(f"Total Estimated Cost: ${total_cost:.2f}") + + +def get_costs_gpt4o(response) -> None: + MODEL_INPUT = OMNI_INPUT / 1_000_000 + MODEL_OUTPUT = OMNI_OUTPUT / 1_000_000 + + print("\nActual Cost: ") + i_tokens = response.usage.prompt_tokens + o_tokens = response.usage.completion_tokens + print(f"Tokens: {i_tokens} + {o_tokens}") + + input_cost = i_tokens * MODEL_INPUT + output_cost = o_tokens * MODEL_OUTPUT + total_cost = input_cost + output_cost + + print(f"Cost: {input_cost:.4f} + {output_cost:.4f}") + print(f"Total Cost: ${total_cost:.2f}") + + +def get_n_tokens(model: str, text: str) -> int: + encoding = tiktoken.encoding_for_model(model) + tokenized = encoding.encode(text) + n_tokens = len(tokenized) + return n_tokens + + +def ask_permission(): + while True: + response = input("Are you sure you want to continue? (y/n): ").strip().lower() + if response == "y": + print("Permission granted. Continuing...") + return True + elif response == "n": + print("Permission denied. Exiting...") + return False + else: + print("Invalid input. Please enter 'y' or 'n'.") + +