From 444faa8dda0c8c78a84bda03118a8efbe374099b Mon Sep 17 00:00:00 2001 From: Nicole White Date: Thu, 22 Feb 2024 13:43:54 -0500 Subject: [PATCH] Add testing example --- .github/workflows/autoblocks-testing.yml | 40 ++ Python/testing-sdk/README.md | 117 +++++ Python/testing-sdk/my_project/__init__.py | 0 .../my_project/evaluators/__init__.py | 0 .../my_project/evaluators/has_substrings.py | 61 +++ .../my_project/evaluators/is_valid_json.py | 47 ++ Python/testing-sdk/my_project/run.py | 11 + .../testing-sdk/my_project/tasks/__init__.py | 0 .../my_project/tasks/flashcard_generator.py | 121 +++++ .../my_project/tasks/study_guide_outline.py | 34 ++ .../my_project/test_suites/__init__.py | 0 .../flashcard_generator/__init__.py | 28 ++ .../flashcard_generator/evaluators.py | 134 ++++++ .../flashcard_generator/test_cases.py | 140 ++++++ .../study_guide_outline/__init__.py | 27 ++ .../study_guide_outline/evaluators.py | 43 ++ .../study_guide_outline/test_cases.py | 58 +++ .../my_project/test_suites/util.py | 5 + Python/testing-sdk/poetry.lock | 451 ++++++++++++++++++ Python/testing-sdk/pyproject.toml | 18 + README.md | 1 + 21 files changed, 1336 insertions(+) create mode 100644 .github/workflows/autoblocks-testing.yml create mode 100644 Python/testing-sdk/README.md create mode 100644 Python/testing-sdk/my_project/__init__.py create mode 100644 Python/testing-sdk/my_project/evaluators/__init__.py create mode 100644 Python/testing-sdk/my_project/evaluators/has_substrings.py create mode 100644 Python/testing-sdk/my_project/evaluators/is_valid_json.py create mode 100644 Python/testing-sdk/my_project/run.py create mode 100644 Python/testing-sdk/my_project/tasks/__init__.py create mode 100644 Python/testing-sdk/my_project/tasks/flashcard_generator.py create mode 100644 Python/testing-sdk/my_project/tasks/study_guide_outline.py create mode 100644 Python/testing-sdk/my_project/test_suites/__init__.py create mode 100644 Python/testing-sdk/my_project/test_suites/flashcard_generator/__init__.py create mode 100644 Python/testing-sdk/my_project/test_suites/flashcard_generator/evaluators.py create mode 100644 Python/testing-sdk/my_project/test_suites/flashcard_generator/test_cases.py create mode 100644 Python/testing-sdk/my_project/test_suites/study_guide_outline/__init__.py create mode 100644 Python/testing-sdk/my_project/test_suites/study_guide_outline/evaluators.py create mode 100644 Python/testing-sdk/my_project/test_suites/study_guide_outline/test_cases.py create mode 100644 Python/testing-sdk/my_project/test_suites/util.py create mode 100644 Python/testing-sdk/poetry.lock create mode 100644 Python/testing-sdk/pyproject.toml diff --git a/.github/workflows/autoblocks-testing.yml b/.github/workflows/autoblocks-testing.yml new file mode 100644 index 00000000..851a78fa --- /dev/null +++ b/.github/workflows/autoblocks-testing.yml @@ -0,0 +1,40 @@ +name: Autoblocks Testing + +on: + push: # Run on every push. + schedule: # Run every day at ~7:17am PST. + - cron: '17 15 * * *' + +jobs: + py: + runs-on: ubuntu-latest + + defaults: + run: + shell: bash + working-directory: Python/testing-sdk + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup python + uses: actions/setup-python@v5 + with: + python-version-file: '3.11' + + - name: Install poetry + run: curl -sSL https://install.python-poetry.org | python3 - + + - name: Check pyproject.toml & poetry.lock are in sync + run: poetry lock --check + + - name: Install dependencies + run: poetry install + + - name: Run Autoblocks tests + run: npx autoblocks testing exec -- poetry run start + env: + # Add your OpenAI API key & Autoblocks API key to the repository secrets. + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + AUTOBLOCKS_API_KEY: ${{ secrets.AUTOBLOCKS_API_KEY }} diff --git a/Python/testing-sdk/README.md b/Python/testing-sdk/README.md new file mode 100644 index 00000000..8c5226c4 --- /dev/null +++ b/Python/testing-sdk/README.md @@ -0,0 +1,117 @@ +

+ +

+

+ 📚 + Documentation +   + • +   + 🖥️ + Application +   + • +   + ☎️ + Meet with Autoblocks Engineering +

+ +## Setup + +### Install [`poetry`](https://python-poetry.org/) + +```bash +curl -sSL https://install.python-poetry.org | python3 - +``` + +### Install [`npm`](https://docs.npmjs.com/about-npm) + +> **_NOTE:_** You might already have this installed. Check with `npm -v`. + +If you don't have `node` or `npm` installed, we recommend you use `nvm` to do so: + +#### Install [`nvm`](https://github.com/nvm-sh/nvm) + +```bash +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash +``` + +#### Install `node` and `npm` + +```bash +nvm install node +``` + +#### Set the default version when starting a new shell + +```bash +nvm alias default node +``` + +### Install dependencies + +``` +poetry install +``` + +## Run Autoblocks tests + +### Set your Autoblocks API key + +Retrieve your **local testing API key** from the [settings page](https://app.autoblocks.ai/settings/api-keys) and set it as an environment variable: + +```bash +export AUTOBLOCKS_API_KEY=... +``` + +### Set your OpenAI API key + +```bash +export OPENAI_API_KEY=... +``` + +### Run the tests + +```bash +npx autoblocks testing exec -m "my first run" -- poetry run start +``` + +You should see something like: + +Screenshot 2024-02-22 at 1 23 50 PM + +You can click on the links next to each test name to dig into more details. +You can also find all of your tests on the testing homepage in the [Autoblocks application](https://app.autoblocks.ai/testing/local). + +## GitHub Actions setup + +A starter workflow was added in [`.github/workflows/autoblocks-testing.yml`](./.github/workflows/ci.yml). +This workflow runs the tests on every push to the repository and also +on a daily schedule. + +To use this workflow, `AUTOBLOCKS_API_KEY` and `OPENAI_API_KEY` must be set as repository secrets. +You can add these by going to **Settings** > **Secrets and variables** > **Actions** > **New repository secret** + +![repository-secrets](./media/repository-secrets.png) + +## Repo structure + +``` +my_project/ + run.py <-- imports all tests from tests/ and runs them + evaluators/ <-- all common evaluators are implemented here + some_shared_evaluator1.py + some_shared_evaluator2.py + tasks/ <-- all "tasks" are implemented here + task1.py + task2.py + test_suites/ <-- tests for each task + task1/ + __init__.py <-- implements the runner for task1 + evaluators.py <-- evaluators used only for task1 + test_cases.py <-- contains test cases for task1 + task2/ + __init__.py <-- implements the runner for task2 + evaluators.py <-- evaluators used only for task2 + test_cases.py <-- contains test cases for task2 +``` diff --git a/Python/testing-sdk/my_project/__init__.py b/Python/testing-sdk/my_project/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Python/testing-sdk/my_project/evaluators/__init__.py b/Python/testing-sdk/my_project/evaluators/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Python/testing-sdk/my_project/evaluators/has_substrings.py b/Python/testing-sdk/my_project/evaluators/has_substrings.py new file mode 100644 index 00000000..6738e554 --- /dev/null +++ b/Python/testing-sdk/my_project/evaluators/has_substrings.py @@ -0,0 +1,61 @@ +import abc +from typing import Any +from typing import List +from typing import Optional + +from autoblocks.testing.models import BaseEvaluator +from autoblocks.testing.models import BaseTestCase +from autoblocks.testing.models import Evaluation +from autoblocks.testing.models import Threshold + + +class BaseHasSubstrings(BaseEvaluator, abc.ABC): + id = "has-substrings" + + """ + Can be overriden by subclassing and changing this threshold. For example: + + class MyEvaluator(HasSubstrings): + threshold = None + + run_test_suite( + ... + evaluators=[ + MyEvaluator() + ], + ... + ) + """ + threshold: Optional[Threshold] = Threshold(gte=1) + + @abc.abstractmethod + def expected_substrings(self, test_case: BaseTestCase) -> List[str]: + """ + Required to be implemented by the subclass. + + In most cases this will just return the field from the test case that contains the expected substrings, + but since it's a method, it can be used to calculate the expected substrings in a more complex way + if appropriate. + + For example: + + class MyEvaluator(HasSubstrings): + def expected_substrings(self, test_case: MyTestCase) -> List[str]: + return test_case.expected_substrings + """ + ... + + def output_as_str(self, output: Any) -> str: + """ + Can be overriden by the subclass to change how the output is converted to a string. + """ + return str(output) + + def evaluate(self, test_case: BaseTestCase, output: Any) -> Evaluation: + expected_substrings = self.expected_substrings(test_case) + output_as_str = self.output_as_str(output) + + for substring in expected_substrings: + if substring not in output_as_str: + return Evaluation(score=0, threshold=self.threshold) + return Evaluation(score=1, threshold=self.threshold) diff --git a/Python/testing-sdk/my_project/evaluators/is_valid_json.py b/Python/testing-sdk/my_project/evaluators/is_valid_json.py new file mode 100644 index 00000000..2bf82a31 --- /dev/null +++ b/Python/testing-sdk/my_project/evaluators/is_valid_json.py @@ -0,0 +1,47 @@ +import json +from typing import Any +from typing import Optional + +from autoblocks.testing.models import BaseEvaluator +from autoblocks.testing.models import BaseTestCase +from autoblocks.testing.models import Evaluation +from autoblocks.testing.models import Threshold + + +class IsValidJson(BaseEvaluator): + id = "is-valid-json" + + """ + Can be overriden by subclassing and changing this threshold. For example: + + class MyEvaluator(IsValidJson): + threshold = None + + run_test_suite( + ... + evaluators=[ + MyEvaluator() + ], + ... + ) + """ + threshold: Optional[Threshold] = Threshold(gte=1) + + def output_as_str(self, output: Any) -> str: + """ + Can be overriden by the subclass to change how the output is converted to a string. + + For example: + + class MyEvaluator(IsValidJson): + def output_as_str(self, output: SomeCustomOutputType) -> str: + return output.as_json() + """ + return str(output) + + def evaluate(self, test_case: BaseTestCase, output: Any) -> Evaluation: + try: + json.loads(self.output_as_str(output)) + return Evaluation(score=1, threshold=self.threshold) + except json.JSONDecodeError: + return Evaluation(score=0, threshold=self.threshold) diff --git a/Python/testing-sdk/my_project/run.py b/Python/testing-sdk/my_project/run.py new file mode 100644 index 00000000..0c640e9e --- /dev/null +++ b/Python/testing-sdk/my_project/run.py @@ -0,0 +1,11 @@ +from my_project.test_suites import flashcard_generator +from my_project.test_suites import study_guide_outline + + +def run(): + # Autoblocks handles running these tests asynchronously behind the scenes + # in a dedicated event loop, so no need to attempt to add any concurrency + # here or use asyncio.run() or similar. Just simply call each test suite's + # run() function and Autoblocks will handle the rest. + flashcard_generator.run() + study_guide_outline.run() diff --git a/Python/testing-sdk/my_project/tasks/__init__.py b/Python/testing-sdk/my_project/tasks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Python/testing-sdk/my_project/tasks/flashcard_generator.py b/Python/testing-sdk/my_project/tasks/flashcard_generator.py new file mode 100644 index 00000000..cf24f9b4 --- /dev/null +++ b/Python/testing-sdk/my_project/tasks/flashcard_generator.py @@ -0,0 +1,121 @@ +import dataclasses +import json +from typing import List + +from openai import OpenAI +from openai.types.chat.completion_create_params import ResponseFormat + +openai_client = OpenAI() + + +@dataclasses.dataclass() +class Flashcard: + front: str + back: str + + +system_prompt = """Given a user's notes, generate flashcards that will allow the user to study those notes. + +Your first task is to identify the facts or key points in the notes. +Then, create a flashcard for each fact or key point. +The front of the flashcard should be a question, and the back of the flashcard should be the answer to that question. +Each flashcard should be supported by content from the notes. +Ignore the tone of the notes and always make the flashcards in a professional tone. +Ignore any subjective commentary in the notes and only focus on the facts or key points. +Return the results as JSON in the below format: + +``` +{ + "cards": [ + { + "front": "What is the capital of France?", + "back": "Paris" + }, + { + "front": "Who painted the Mona Lisa?", + "back": "Leonardo da Vinci" + } + ] +} +``` + +Only return JSON in your response, nothing else. Do not include the backticks. + +Example: + +Notes: + +''' +Am. History Notes 🇺🇸 +Beginnings & Stuff +Columbus 1492, "found" America but actually not the first. +Native Americans were here first, tons of diff cultures. +Colonies & Things +13 Colonies cuz Brits wanted $ and land. +Taxation w/o Representation = Colonists mad at British taxes, no say in gov. +Boston Tea Party = Tea in the harbor, major protest. +Revolution Time +Declaration of Independence, 1776, basically "we're breaking up with you, Britain". +George Washington = First pres, war hero. +Moving West +Manifest Destiny = Idea that the US was supposed to own all land coast to coast. +Louisiana Purchase, 1803, Thomas Jefferson bought a ton of land from France. +''' + +Flashcards: + +{ + "cards": [ + { + "front": "Who was the first president of the United States?", + "back": "George Washington" + }, + { + "front": "What was the idea that the US was supposed to own all land coast to coast?", + "back": "Manifest Destiny" + }, + { + "front": "What was the year of the Louisiana Purchase?", + "back": "1803" + } + ] +} +""" + +user_prompt = """Notes: + +''' +{notes} +''' + +Flashcards:""" + + +def gen_flashcards_from_notes(notes: str) -> List[Flashcard]: + """ + Generates flashcards based on a user's notes. + """ + response = openai_client.chat.completions.create( + model="gpt-3.5-turbo-1106", + temperature=0.0, + response_format=ResponseFormat(type="json_object"), + messages=[ + dict( + role="system", + content=system_prompt, + ), + dict( + role="user", + content=user_prompt.format(notes=notes), + ), + ], + ) + raw_content = response.choices[0].message.content.strip() + parsed_content = json.loads(raw_content) + return [ + Flashcard( + front=parsed_content["front"], + back=parsed_content["back"], + ) + for parsed_content in parsed_content["cards"] + ] diff --git a/Python/testing-sdk/my_project/tasks/study_guide_outline.py b/Python/testing-sdk/my_project/tasks/study_guide_outline.py new file mode 100644 index 00000000..5c6f8119 --- /dev/null +++ b/Python/testing-sdk/my_project/tasks/study_guide_outline.py @@ -0,0 +1,34 @@ +from openai import OpenAI + +openai_client = OpenAI() + +system_prompt = """Generate a study guide outline for a given topic. +It should be a bulleted list with just the title of each category. +The top level bullets should be stars: * +The second level bullets should be dashes: - +The second level dashes should have two spaces before them. +The study guide should be no more than two levels deep. +There should be between five and ten top-level categories.""" + + +def gen_study_guide_outline(topic: str) -> str: + """ + Generates a bulleted study guide outline for a given topic. + """ + response = openai_client.chat.completions.create( + model="gpt-3.5-turbo-1106", + temperature=0.5, + max_tokens=1_000, + n=1, + messages=[ + dict( + role="system", + content=system_prompt, + ), + dict( + role="user", + content=f"Topic: {topic}", + ), + ], + ) + return response.choices[0].message.content.strip() diff --git a/Python/testing-sdk/my_project/test_suites/__init__.py b/Python/testing-sdk/my_project/test_suites/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Python/testing-sdk/my_project/test_suites/flashcard_generator/__init__.py b/Python/testing-sdk/my_project/test_suites/flashcard_generator/__init__.py new file mode 100644 index 00000000..6153357b --- /dev/null +++ b/Python/testing-sdk/my_project/test_suites/flashcard_generator/__init__.py @@ -0,0 +1,28 @@ +from typing import List + +from autoblocks.testing.run import run_test_suite + +from my_project.tasks.flashcard_generator import Flashcard +from my_project.tasks.flashcard_generator import gen_flashcards_from_notes +from my_project.test_suites.flashcard_generator.evaluators import IsProfessionalTone +from my_project.test_suites.flashcard_generator.evaluators import IsSupportedByNotes +from my_project.test_suites.flashcard_generator.test_cases import TestCase +from my_project.test_suites.flashcard_generator.test_cases import gen_test_cases + + +def test_fn(test_case: TestCase) -> List[Flashcard]: + return gen_flashcards_from_notes(test_case.notes) + + +def run(): + run_test_suite( + id="flashcard-generator", + test_cases=gen_test_cases(), + evaluators=[ + IsSupportedByNotes(), + IsProfessionalTone(), + ], + fn=test_fn, + max_test_case_concurrency=5, + max_evaluator_concurrency=1, + ) diff --git a/Python/testing-sdk/my_project/test_suites/flashcard_generator/evaluators.py b/Python/testing-sdk/my_project/test_suites/flashcard_generator/evaluators.py new file mode 100644 index 00000000..257a8b9e --- /dev/null +++ b/Python/testing-sdk/my_project/test_suites/flashcard_generator/evaluators.py @@ -0,0 +1,134 @@ +import asyncio +from typing import List + +from autoblocks.testing.models import BaseEvaluator +from autoblocks.testing.models import Evaluation +from openai import AsyncOpenAI + +from my_project.tasks.flashcard_generator import Flashcard +from my_project.test_suites.flashcard_generator.test_cases import TestCase + +openai_client = AsyncOpenAI() + + +class IsProfessionalTone(BaseEvaluator): + id = "is-professional-tone" + + prompt = """Please evaluate the provided text for its professionalism in the context of formal communication. +Consider the following criteria in your assessment: + +Language Use: Formality, clarity, and precision of language without slang or casual expressions. +Sentence Structure: Logical and well-formed sentence construction without run-ons or fragments. +Tone and Style: Respectful, objective, and appropriately formal tone without bias or excessive emotionality. +Grammar and Punctuation: Correct grammar, punctuation, and capitalization. +Based on these criteria, provide a binary response where: + +0 indicates the text does not maintain a professional tone. +1 indicates the text maintains a professional tone. +No further explanation or summary is required; just provide the number that represents your assessment. +""" + + async def score_flashcard(self, flashcard: Flashcard) -> int: + content = f"{flashcard.front}\n{flashcard.back}" + + response = await openai_client.chat.completions.create( + model="gpt-3.5-turbo-1106", + temperature=0.0, + n=1, + max_tokens=1, + messages=[ + dict( + role="system", + content=self.prompt, + ), + dict( + role="user", + content=content, + ), + ], + ) + raw_content = response.choices[0].message.content.strip() + if raw_content == "0": + return 0 + elif raw_content == "1": + return 1 + + raise ValueError(f"Unexpected response: {raw_content}") + + async def evaluate( + self, test_case: TestCase, output: List[Flashcard] + ) -> Evaluation: + # Score each flashcard asynchronously + scores = await asyncio.gather( + *[self.score_flashcard(flashcard) for flashcard in output] + ) + if not scores: + raise RuntimeError("No scores were returned") + + # Return the average score as the evaluation score + return Evaluation(score=sum(scores) / len(scores)) + + +class IsSupportedByNotes(BaseEvaluator): + id = "is-supported-by-notes" + + prompt = """Given some notes by a student and a flashcard in the form of a question and answer, evaluate whether the flashcard's question and answer are supported by the notes. +It's possible the question and answer aren't in the notes verbatim. +If the notes provide enough context or information to support the question and answer, consider that sufficient support. +Based on these criteria, provide a binary response where: +0 indicates the flashcard's question and answer are not supported by the notes. +1 indicates the flashcard's question and answer are supported by the notes. +No further explanation or summary is required; just provide the number that represents your assessment.""" # noqa: E501 + + async def score_flashcard(self, test_case: TestCase, flashcard: Flashcard) -> int: + content = f"""Notes: + + ''' + {test_case.notes} + ''' + + Flashcard: + + Question: {flashcard.front} + Answer: {flashcard.back} + """ + + response = await openai_client.chat.completions.create( + model="gpt-3.5-turbo-1106", + temperature=0.0, + n=1, + max_tokens=1, + messages=[ + dict( + role="system", + content=self.prompt, + ), + dict( + role="user", + content=content, + ), + ], + ) + raw_content = response.choices[0].message.content.strip() + if raw_content == "0": + return 0 + elif raw_content == "1": + return 1 + + raise ValueError(f"Unexpected response: {raw_content}") + + async def evaluate( + self, test_case: TestCase, output: List[Flashcard] + ) -> Evaluation: + """ + Return the percent of flashcards whose questions and answers are supported by the notes. + """ + # Score each flashcard asynchronously + scores = await asyncio.gather( + *[self.score_flashcard(test_case, flashcard) for flashcard in output] + ) + if not scores: + raise RuntimeError("No scores were returned") + + # Return the average score as the evaluation score + return Evaluation(score=sum(scores) / len(scores)) diff --git a/Python/testing-sdk/my_project/test_suites/flashcard_generator/test_cases.py b/Python/testing-sdk/my_project/test_suites/flashcard_generator/test_cases.py new file mode 100644 index 00000000..cf88938f --- /dev/null +++ b/Python/testing-sdk/my_project/test_suites/flashcard_generator/test_cases.py @@ -0,0 +1,140 @@ +import dataclasses +from typing import List + +from autoblocks.testing.models import BaseTestCase + +from my_project.test_suites.util import md5 + + +@dataclasses.dataclass() +class TestCase(BaseTestCase): + notes: str + + def hash(self) -> str: + """ + This hash serves as a unique identifier for a test case throughout its lifetime. + """ + return md5(self.notes) + + +def gen_test_cases() -> List[TestCase]: + return [ + TestCase( + notes="""Bio 101 Notes +Cells n stuff +Cells are like, the smallest thingies that are alive. +Some old dude named Hooke found them in 1665 by looking at cork. +2 kinds: Prokaryotic (no nucleus, think bacteria) & Eukaryotic (has a nucleus, like us and plants). +Cell Theory (important!!) +Everything alive = made of cells. +Cells = life's basic unit. +New cells come from old ones. +Parts of a Cell (the bits and pieces) +Cell Membrane: kinda like a bouncer, decides what gets in and out. +Nucleus: boss of the cell, has all the DNA. +Mitochondria: power station, makes energy. +Ribosomes: tiny factories for making proteins. +ER stuff: +Rough ER has ribosomes, makes proteins. +Smooth ER is like, no ribosomes, makes fats. +Golgi Thingy: packages proteins. +Lysosomes: trash disposals for cells. +Plants have extra stuff: +Chloroplasts for catching sunlight. +Cell Wall for extra toughness. +Membrane and Moving Stuff +Phospholipid bilayer = fancy term for the cell membrane structure. +It's picky about what it lets in/out. +Doing Things (Cellular Processes) +Photosynthesis: Only in plants, turns sunlight to food. +Breathing in Cells (Respiration): Turning food & O2 into energy. +Cell Division: Mitosis (for growing and fixing) & Meiosis (making baby cells). +DNA & Genes +DNA = double helix thing, basically the recipe book for making you. +Genes = specific recipes for traits like eye color. +Evolution (Darwin’s big idea) +Survival of the fittest. +Animals change over time to become better at surviving. +Random Notes: +Need to remember: Cell wall = plants only. +Mitochondria and chloroplasts have their own DNA?? Check this. +DNA to protein = transcription and translation (need to clarify). +Why does rough ER look bumpy under a microscope? Oh, because of ribosomes. +Evolution examples for exam?""", + ), + TestCase( + notes="""Eng Lit Notes +Random Stuff on Books & Authors +Shakespeare (Big Deal) + +Wrote plays and sonnets. +Old English (hard to read lol). +Famous stuff: "Romeo & Juliet", "Hamlet", "Macbeth". +Themes: love, power, betrayal, the supernatural. +Chaucer’s "Canterbury Tales" + +Super old stories, like medieval road trip. +Different people telling tales, some funny, some serious. +Middle English (even harder to read). +American Lit Bits + +Mark Twain: "Huckleberry Finn" = kid on a raft, talks about racism, freedom. +F. Scott Fitzgerald: "The Great Gatsby", 1920s jazz age, American Dream is kinda questioned. +Poetry Stuff +Poems = lots of feelings in few words. +Rhyme, rhythm, metaphors. +Emily Dickinson: Weird punctuation, lots of dashes, wrote about death and nature. +Robert Frost: "The Road Not Taken", about choices and life paths. +Modern Stuff (Kinda) +"To Kill a Mockingbird" by Harper Lee: Racism, growing up, the South. +"1984" by George Orwell: Creepy government watching everyone. +"The Catcher in the Rye" by J.D. Salinger: Teen angst, rebellion. +Themes & Symbols +Symbols: Stuff in books that stands for other stuff. Like, a road in a poem might not just be a road. +Themes: Big ideas in a story. Freedom, identity, conflict, etc. +Notes to Self: +Shakespeare invented a ton of words, look up some. +Need examples of irony from "The Great Gatsby". +What the heck is iambic pentameter again? +Look up what "postmodernism" means. +Remember to find quotes for essay on "Mockingbird". +Random Thoughts: +Why do all old books have to be tragic? +Need to watch some Shakespeare adaptations to get it better. +Symbols in "The Great Gatsby"? Green light = dream?? +Is every old poem about death or what?""" + ), + TestCase( + notes="""Early Stuff +Stonehenge: Big rocks in a circle, super old, no one knows why they did it. +Romans: Came, saw, conquered. Left a bunch of baths and walls (Hadrian's Wall). +Medieval Mayhem +1066: Normans (French guys) invade, William the Conqueror becomes king. +Magna Carta (1215): King John forced to sign it, basically "Kings can't do whatever they want." +Wars & Plagues +100 Years War: England vs. France, forever fighting. +Black Death: Wipes out like half the population. Seriously bad. +Tudor Drama +Henry VIII: Marries a bunch of women, starts his own church (Church of England) because the Pope won't let him divorce. +Elizabeth I: Virgin Queen, beats the Spanish Armada, arts and theatre flourish (Shakespeare time). +Civil War & The Commonwealth +1642-1651: Civil War, Charles I loses his head, literally. +Oliver Cromwell: Becomes "Lord Protector", basically a dictator but not called a king. +Restoration to Revolution +1660: Monarchy's back with Charles II. +1688: Glorious Revolution, William of Orange takes over, more power to Parliament. +Industrial Revolution +18th-19th Century: Everything changes, factories everywhere, British Empire expands big time. +20th Century Stuff +WWI & WWII: Major world wars, lots of impact. +Decolonization: Empire shrinks, countries gain independence. +Modern Bits +EU & Brexit: Joining and leaving the European Union. +Monarchs: From Elizabeth II to Charles III, royal family drama continues. +Random Thoughts: +Why so many Henrys and Edwards? +Need to remember dates for exams (ugh). +The industrial revolution = coal, steam, and smog. +How did Britain end up ruling so much of the world?""" + ), + ] diff --git a/Python/testing-sdk/my_project/test_suites/study_guide_outline/__init__.py b/Python/testing-sdk/my_project/test_suites/study_guide_outline/__init__.py new file mode 100644 index 00000000..0cd0f49f --- /dev/null +++ b/Python/testing-sdk/my_project/test_suites/study_guide_outline/__init__.py @@ -0,0 +1,27 @@ +from autoblocks.testing.run import run_test_suite + +from my_project.tasks.study_guide_outline import gen_study_guide_outline +from my_project.test_suites.study_guide_outline.evaluators import Formatting +from my_project.test_suites.study_guide_outline.evaluators import HasSubstrings +from my_project.test_suites.study_guide_outline.evaluators import NumCategories +from my_project.test_suites.study_guide_outline.test_cases import TestCase +from my_project.test_suites.study_guide_outline.test_cases import gen_test_cases + + +def test_fn(test_case: TestCase) -> str: + return gen_study_guide_outline(test_case.topic) + + +def run(): + run_test_suite( + id="study-guide-outline", + test_cases=gen_test_cases(), + evaluators=[ + Formatting(), + NumCategories(), + HasSubstrings(), + ], + fn=test_fn, + max_test_case_concurrency=5, + max_evaluator_concurrency=2, + ) diff --git a/Python/testing-sdk/my_project/test_suites/study_guide_outline/evaluators.py b/Python/testing-sdk/my_project/test_suites/study_guide_outline/evaluators.py new file mode 100644 index 00000000..16111f3c --- /dev/null +++ b/Python/testing-sdk/my_project/test_suites/study_guide_outline/evaluators.py @@ -0,0 +1,43 @@ +from typing import List + +from autoblocks.testing.models import BaseEvaluator +from autoblocks.testing.models import Evaluation +from autoblocks.testing.models import Threshold + +from my_project.evaluators.has_substrings import BaseHasSubstrings +from my_project.test_suites.study_guide_outline.test_cases import TestCase + + +class Formatting(BaseEvaluator): + id = "formatting" + + @staticmethod + def score(output: str) -> int: + """ + Every line should either be blank or start with "* " or " - " + """ + for line in output.splitlines(): + if not (line.strip() == "" or line.startswith(("* ", " - "))): + return 0 + return 1 + + def evaluate(self, test_case: TestCase, output: str) -> Evaluation: + return Evaluation(score=self.score(output), threshold=Threshold(gte=1)) + + +class NumCategories(BaseEvaluator): + id = "num-categories" + + min_categories: int = 5 + max_categories: int = 10 + + def score(self, output: str) -> int: + return int(self.min_categories <= output.count("* ") <= self.max_categories) + + def evaluate(self, test_case: TestCase, output: str) -> Evaluation: + return Evaluation(score=self.score(output), threshold=Threshold(gte=1)) + + +class HasSubstrings(BaseHasSubstrings): + def expected_substrings(self, test_case: TestCase) -> List[str]: + return test_case.expected_substrings diff --git a/Python/testing-sdk/my_project/test_suites/study_guide_outline/test_cases.py b/Python/testing-sdk/my_project/test_suites/study_guide_outline/test_cases.py new file mode 100644 index 00000000..62cf3d1a --- /dev/null +++ b/Python/testing-sdk/my_project/test_suites/study_guide_outline/test_cases.py @@ -0,0 +1,58 @@ +import dataclasses +from typing import List + +from autoblocks.testing.models import BaseTestCase + +from my_project.test_suites.util import md5 + + +@dataclasses.dataclass() +class TestCase(BaseTestCase): + topic: str + expected_substrings: List[str] + + def hash(self) -> str: + """ + This hash serves as a unique identifier for a test case throughout its lifetime. + """ + return md5(self.topic) + + +def gen_test_cases() -> List[TestCase]: + return [ + TestCase( + topic="Introduction to Organic Chemistry", + expected_substrings=[ + "Functional Groups", + ], + ), + TestCase( + topic="Fundamentals of Calculus", + expected_substrings=[ + "Derivatives", + "Differentiation", + ], + ), + TestCase( + topic="World History: Ancient Civilizations", + expected_substrings=[ + "Mesopotamia", + "Egypt", + ], + ), + TestCase( + topic="Basics of Programming in Python", + expected_substrings=[ + "Syntax", + "Variables", + "Functions", + ], + ), + TestCase( + topic="Principles of Economics", + expected_substrings=[ + "Microeconomics", + "Macroeconomics", + ], + ), + ] diff --git a/Python/testing-sdk/my_project/test_suites/util.py b/Python/testing-sdk/my_project/test_suites/util.py new file mode 100644 index 00000000..28979812 --- /dev/null +++ b/Python/testing-sdk/my_project/test_suites/util.py @@ -0,0 +1,5 @@ +import hashlib + + +def md5(text: str) -> str: + return hashlib.md5(text.encode()).hexdigest() diff --git a/Python/testing-sdk/poetry.lock b/Python/testing-sdk/poetry.lock new file mode 100644 index 00000000..94032874 --- /dev/null +++ b/Python/testing-sdk/poetry.lock @@ -0,0 +1,451 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[[package]] +name = "anyio" +version = "4.3.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[package]] +name = "autoblocksai" +version = "0.0.27" +description = "Python client for Autoblocks" +optional = false +python-versions = ">=3.8.1,<4.0.0" +files = [ + {file = "autoblocksai-0.0.27-py3-none-any.whl", hash = "sha256:6fb8976d957503d9ff757c7f224f4fd27ef14b0a69e5456afaa4a513bd5523db"}, + {file = "autoblocksai-0.0.27.tar.gz", hash = "sha256:d31fe964e5a5105d10913a30664a2b356073a7483addf1e23df3902683ddb78e"}, +] + +[package.dependencies] +click = ">=8.0.0" +httpx = ">=0.24.0" +orjson = ">=3.0.0" +pyyaml = ">=6.0.0" + +[[package]] +name = "certifi" +version = "2024.2.2" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "distro" +version = "1.9.0" +description = "Distro - an OS platform information API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, +] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.3" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, + {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.24.0)"] + +[[package]] +name = "httpx" +version = "0.26.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, + {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "openai" +version = "1.12.0" +description = "The official Python library for the openai API" +optional = false +python-versions = ">=3.7.1" +files = [ + {file = "openai-1.12.0-py3-none-any.whl", hash = "sha256:a54002c814e05222e413664f651b5916714e4700d041d5cf5724d3ae1a3e3481"}, + {file = "openai-1.12.0.tar.gz", hash = "sha256:99c5d257d09ea6533d689d1cc77caa0ac679fa21efef8893d8b0832a86877f1b"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +tqdm = ">4" +typing-extensions = ">=4.7,<5" + +[package.extras] +datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] + +[[package]] +name = "orjson" +version = "3.9.14" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +optional = false +python-versions = ">=3.8" +files = [ + {file = "orjson-3.9.14-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:793f6c9448ab6eb7d4974b4dde3f230345c08ca6c7995330fbceeb43a5c8aa5e"}, + {file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bc7928d161840096adc956703494b5c0193ede887346f028216cac0af87500"}, + {file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:58b36f54da759602d8e2f7dad958752d453dfe2c7122767bc7f765e17dc59959"}, + {file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:abcda41ecdc950399c05eff761c3de91485d9a70d8227cb599ad3a66afe93bcc"}, + {file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df76ecd17b1b3627bddfd689faaf206380a1a38cc9f6c4075bd884eaedcf46c2"}, + {file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d450a8e0656efb5d0fcb062157b918ab02dcca73278975b4ee9ea49e2fcf5bd5"}, + {file = "orjson-3.9.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:95c03137b0cf66517c8baa65770507a756d3a89489d8ecf864ea92348e1beabe"}, + {file = "orjson-3.9.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20837e10835c98973673406d6798e10f821e7744520633811a5a3d809762d8cc"}, + {file = "orjson-3.9.14-cp310-none-win32.whl", hash = "sha256:1f7b6f3ef10ae8e3558abb729873d033dbb5843507c66b1c0767e32502ba96bb"}, + {file = "orjson-3.9.14-cp310-none-win_amd64.whl", hash = "sha256:ea890e6dc1711aeec0a33b8520e395c2f3d59ead5b4351a788e06bf95fc7ba81"}, + {file = "orjson-3.9.14-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c19009ff37f033c70acd04b636380379499dac2cba27ae7dfc24f304deabbc81"}, + {file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19cdea0664aec0b7f385be84986d4defd3334e9c3c799407686ee1c26f7b8251"}, + {file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:135d518f73787ce323b1a5e21fb854fe22258d7a8ae562b81a49d6c7f826f2a3"}, + {file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2cf1d0557c61c75e18cf7d69fb689b77896e95553e212c0cc64cf2087944b84"}, + {file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7c11667421df2d8b18b021223505dcc3ee51be518d54e4dc49161ac88ac2b87"}, + {file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eefc41ba42e75ed88bc396d8fe997beb20477f3e7efa000cd7a47eda452fbb2"}, + {file = "orjson-3.9.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:917311d6a64d1c327c0dfda1e41f3966a7fb72b11ca7aa2e7a68fcccc7db35d9"}, + {file = "orjson-3.9.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4dc1c132259b38d12c6587d190cd09cd76e3b5273ce71fe1372437b4cbc65f6f"}, + {file = "orjson-3.9.14-cp311-none-win32.whl", hash = "sha256:6f39a10408478f4c05736a74da63727a1ae0e83e3533d07b19443400fe8591ca"}, + {file = "orjson-3.9.14-cp311-none-win_amd64.whl", hash = "sha256:26280a7fcb62d8257f634c16acebc3bec626454f9ab13558bbf7883b9140760e"}, + {file = "orjson-3.9.14-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:08e722a8d06b13b67a51f247a24938d1a94b4b3862e40e0eef3b2e98c99cd04c"}, + {file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2591faa0c031cf3f57e5bce1461cfbd6160f3f66b5a72609a130924917cb07d"}, + {file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2450d87dd7b4f277f4c5598faa8b49a0c197b91186c47a2c0b88e15531e4e3e"}, + {file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90903d2908158a2c9077a06f11e27545de610af690fb178fd3ba6b32492d4d1c"}, + {file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce6f095eef0026eae76fc212f20f786011ecf482fc7df2f4c272a8ae6dd7b1ef"}, + {file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:751250a31fef2bac05a2da2449aae7142075ea26139271f169af60456d8ad27a"}, + {file = "orjson-3.9.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9a1af21160a38ee8be3f4fcf24ee4b99e6184cadc7f915d599f073f478a94d2c"}, + {file = "orjson-3.9.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:449bf090b2aa4e019371d7511a6ea8a5a248139205c27d1834bb4b1e3c44d936"}, + {file = "orjson-3.9.14-cp312-none-win_amd64.whl", hash = "sha256:a603161318ff699784943e71f53899983b7dee571b4dd07c336437c9c5a272b0"}, + {file = "orjson-3.9.14-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:814f288c011efdf8f115c5ebcc1ab94b11da64b207722917e0ceb42f52ef30a3"}, + {file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a88cafb100af68af3b9b29b5ccd09fdf7a48c63327916c8c923a94c336d38dd3"}, + {file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba3518b999f88882ade6686f1b71e207b52e23546e180499be5bbb63a2f9c6e6"}, + {file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:978f416bbff9da8d2091e3cf011c92da68b13f2c453dcc2e8109099b2a19d234"}, + {file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75fc593cf836f631153d0e21beaeb8d26e144445c73645889335c2247fcd71a0"}, + {file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d1528db3c7554f9d6eeb09df23cb80dd5177ec56eeb55cc5318826928de506"}, + {file = "orjson-3.9.14-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:7183cc68ee2113b19b0b8714221e5e3b07b3ba10ca2bb108d78fd49cefaae101"}, + {file = "orjson-3.9.14-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:df3266d54246cb56b8bb17fa908660d2a0f2e3f63fbc32451ffc1b1505051d07"}, + {file = "orjson-3.9.14-cp38-none-win32.whl", hash = "sha256:7913079b029e1b3501854c9a78ad938ed40d61fe09bebab3c93e60ff1301b189"}, + {file = "orjson-3.9.14-cp38-none-win_amd64.whl", hash = "sha256:29512eb925b620e5da2fd7585814485c67cc6ba4fe739a0a700c50467a8a8065"}, + {file = "orjson-3.9.14-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5bf597530544db27a8d76aced49cfc817ee9503e0a4ebf0109cd70331e7bbe0c"}, + {file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac650d49366fa41fe702e054cb560171a8634e2865537e91f09a8d05ea5b1d37"}, + {file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:236230433a9a4968ab895140514c308fdf9f607cb8bee178a04372b771123860"}, + {file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3014ccbda9be0b1b5f8ea895121df7e6524496b3908f4397ff02e923bcd8f6dd"}, + {file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac0c7eae7ad3a223bde690565442f8a3d620056bd01196f191af8be58a5248e1"}, + {file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca33fdd0b38839b01912c57546d4f412ba7bfa0faf9bf7453432219aec2df07"}, + {file = "orjson-3.9.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f75823cc1674a840a151e999a7dfa0d86c911150dd6f951d0736ee9d383bf415"}, + {file = "orjson-3.9.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f52ac2eb49e99e7373f62e2a68428c6946cda52ce89aa8fe9f890c7278e2d3a"}, + {file = "orjson-3.9.14-cp39-none-win32.whl", hash = "sha256:0572f174f50b673b7df78680fb52cd0087a8585a6d06d295a5f790568e1064c6"}, + {file = "orjson-3.9.14-cp39-none-win_amd64.whl", hash = "sha256:ab90c02cb264250b8a58cedcc72ed78a4a257d956c8d3c8bebe9751b818dfad8"}, + {file = "orjson-3.9.14.tar.gz", hash = "sha256:06fb40f8e49088ecaa02f1162581d39e2cf3fd9dbbfe411eb2284147c99bad79"}, +] + +[[package]] +name = "pydantic" +version = "2.6.1" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"}, + {file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.16.2" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.16.2" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"}, + {file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"}, + {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"}, + {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"}, + {file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"}, + {file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"}, + {file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"}, + {file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"}, + {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"}, + {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"}, + {file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"}, + {file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"}, + {file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"}, + {file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"}, + {file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"}, + {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"}, + {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"}, + {file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"}, + {file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"}, + {file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"}, + {file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"}, + {file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"}, + {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"}, + {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"}, + {file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"}, + {file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"}, + {file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"}, + {file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"}, + {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"}, + {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"}, + {file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"}, + {file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"}, + {file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "tqdm" +version = "4.66.2" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, + {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "typing-extensions" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "4925a91a5164a9e0abfb898b49ef93fdd270d5868d8002f5255df93dcceab0f4" diff --git a/Python/testing-sdk/pyproject.toml b/Python/testing-sdk/pyproject.toml new file mode 100644 index 00000000..4aa409de --- /dev/null +++ b/Python/testing-sdk/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "testing-sdk" +version = "0.0.0" +description = "Using the Autoblocks Testing SDK for experimentation and regression testing" +authors = [ + "Autoblocks Engineering ", +] +readme = "README.md" +packages = [{include = "my_project"}] + +[tool.poetry.dependencies] +python = "^3.11" +autoblocksai = ">=0.0.27" +openai = "^1.0.0" + +[tool.poetry.scripts] +# This will execute the run() function in my_project/run.py +start = "my_project.run:run" diff --git a/README.md b/README.md index a1580906..424f69b9 100644 --- a/README.md +++ b/README.md @@ -57,5 +57,6 @@ | [flask](/Python/flask) | Autoblocks tracing within a [Flask](https://flask.palletsprojects.com/) application | | [openai-tracing](/Python/openai-tracing) | Tracing of openai calls | | [prompt-sdk-headless](/Python/prompt-sdk-headless) | Safely integrate prompts managed in the Autoblocks platform into your application | +| [testing-sdk](/Python/testing-sdk) | Using the Autoblocks Testing SDK for experimentation and regression testing |