├── .github └── workflows │ ├── publish.yaml │ ├── release.yaml │ └── test.yaml ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── examples ├── bottle.py ├── preset_simple_bottle.py └── roleplay.py ├── pdm.lock ├── pyproject.toml ├── src └── prompt_bottle │ ├── __init__.py │ ├── bottle.py │ ├── presets │ └── simple.py │ ├── tags │ ├── __init__.py │ ├── convert_to.py │ └── tags.py │ └── utils.py └── tests ├── __init__.py └── test_bottle.py /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | workflow_dispatch: 8 | inputs: 9 | repository: 10 | description: 'PyPI repository to publish to (pypi or testpypi)' 11 | required: true 12 | default: 'pypi' 13 | 14 | jobs: 15 | publish: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | id-token: write # Required for private repositories and trusted publishers 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: Setup PDM 25 | uses: pdm-project/setup-pdm@v4 26 | 27 | - name: Build Package 28 | run: pdm build 29 | 30 | 31 | - name: Set Repository 32 | if: github.event_name == 'workflow_dispatch' 33 | run: echo "PYPI_REPOSITORY=${{ github.event.inputs.repository }}" >> $GITHUB_ENV 34 | 35 | - name: Publish Package 36 | run: | 37 | pdm publish --repository ${{ env.PYPI_REPOSITORY || 'pypi' }} 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '[0-9]+.[0-9]+.[0-9]+' # Matches semantic versioning tags like 1.2.3 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write # Needed for creating releases 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 # Fetch all history for changelog generation 19 | 20 | - name: Get tag name 21 | id: get_tag 22 | run: echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT 23 | 24 | - name: Check version in pyproject.toml 25 | id: check_version 26 | run: | 27 | VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/') 28 | if [ "$VERSION" != "${{ steps.get_tag.outputs.TAG_NAME }}" ]; then 29 | echo "Version mismatch: pyproject.toml version is $VERSION, but tag is ${{ steps.get_tag.outputs.TAG_NAME }}" 30 | exit 1 31 | fi 32 | 33 | - name: Generate changelog 34 | id: changelog 35 | run: | 36 | # Get all commits since last tag 37 | PREVIOUS_TAG=$(git describe --tags --abbrev=0 ${{ steps.get_tag.outputs.TAG_NAME }}^ 2>/dev/null || echo "") 38 | if [ -z "$PREVIOUS_TAG" ]; then 39 | # If no previous tag exists, get all commits 40 | CHANGELOG=$(git log --pretty=format:"* %s" ${{ steps.get_tag.outputs.TAG_NAME }}) 41 | else 42 | # Get commits between tags 43 | CHANGELOG=$(git log --pretty=format:"* %s" $PREVIOUS_TAG..${{ steps.get_tag.outputs.TAG_NAME }}) 44 | fi 45 | echo "CHANGELOG<> $GITHUB_OUTPUT 46 | echo "$CHANGELOG" >> $GITHUB_OUTPUT 47 | echo "EOF" >> $GITHUB_OUTPUT 48 | 49 | - name: Create Release 50 | uses: softprops/action-gh-release@v1 51 | with: 52 | tag_name: ${{ steps.get_tag.outputs.TAG_NAME }} 53 | name: Release ${{ steps.get_tag.outputs.TAG_NAME }} 54 | body: | 55 | ## What's Changed 56 | ${{ steps.changelog.outputs.CHANGELOG }} 57 | draft: true 58 | prerelease: false 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest] 13 | python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install pytest 27 | pip install -e . 28 | 29 | - name: Run tests 30 | run: | 31 | pytest tests/ 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm-project.org/#use-with-ide 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.unittestArgs": [ 3 | "-v", 4 | "-s", 5 | "./tests", 6 | "-p", 7 | "test_*.py" 8 | ], 9 | "python.testing.pytestEnabled": false, 10 | "python.testing.unittestEnabled": true 11 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | # Prompt Bottle 6 | 7 | An LLM-targeted template engine, built upon Jinja. 8 | 9 |
10 | 11 | ## Features 12 | 13 | - Use Jinja syntax to build template for LLM inputs list 14 | - Painless multimodal support for LLM inputs 15 | - Use OpenAI chat completion API 16 | 17 | ## Quick Start 18 | 19 | Install the package via pip: 20 | 21 | ```bash 22 | pip install prompt_bottle 23 | ``` 24 | 25 | Then, we can create a "Prompt Bottle" using Jinja syntax: 26 | 27 | ```python 28 | from prompt_bottle import PromptBottle 29 | 30 | bottle = PromptBottle( 31 | [ 32 | { 33 | "role": "system", 34 | "content": "You are a helpful assistant in the domain of {{ domain }}", 35 | }, 36 | "{% for round in rounds %}", 37 | { 38 | "role": "user", 39 | "content": "Question: {{ round[0] }}", 40 | }, 41 | { 42 | "role": "assistant", 43 | "content": "Answer: {{ round[1] }}", 44 | }, 45 | "{% endfor %}", 46 | {"role": "user", "content": "Question: {{ final_question }}"}, 47 | ] 48 | ) 49 | ``` 50 | 51 | Then we can render it as we do with Jinja. 52 | 53 |
54 | Render the bottle and send to OpenAI: 55 | 56 | ```python 57 | from prompt_bottle import pb_img_url 58 | 59 | prompt = bottle.render( 60 | domain="math", 61 | rounds=[ 62 | ("1+1", "2"), 63 | ( 64 | f"What is this picture? {pb_img_url('https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg')}", 65 | "This is an example image by Wikipedia", 66 | ), 67 | ], 68 | final_question="8*8", 69 | ) 70 | 71 | from rich import print # pip install rich 72 | 73 | print(prompt) 74 | ``` 75 | 76 |
77 | It prints the rendered prompt: 78 | 79 | ```python 80 | [ 81 | { 82 | 'content': [{'text': 'You are a helpful assistant in the domain of math', 'type': 'text'}], 83 | 'role': 'system' 84 | }, 85 | {'content': [{'text': 'Question: 1+1', 'type': 'text'}], 'role': 'user'}, 86 | {'role': 'assistant', 'content': [{'text': 'Answer: 2', 'type': 'text'}]}, 87 | { 88 | 'content': [ 89 | {'text': 'Question: What is this picture? ', 'type': 'text'}, 90 | { 91 | 'image_url': {'url': 'https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg'}, 92 | 'type': 'image_url' 93 | } 94 | ], 95 | 'role': 'user' 96 | }, 97 | { 98 | 'role': 'assistant', 99 | 'content': [{'text': 'Answer: This is an example image by Wikipedia', 'type': 'text'}] 100 | }, 101 | {'content': [{'text': 'Question: 8*8', 'type': 'text'}], 'role': 'user'} 102 | ] 103 | ``` 104 |
105 | 106 | Finally, we can send the prompt to OpenAI: 107 | 108 | ```python 109 | from openai import OpenAI 110 | 111 | client = OpenAI() 112 | 113 | response = client.chat.completions.create( 114 | model="gpt-4o-mini", 115 | messages=prompt, 116 | ) 117 | 118 | print(response.choices[0].message.content) 119 | ``` 120 | 121 | The response is: 122 | 123 | ``` 124 | Answer: 64 125 | ``` 126 | 127 |
128 | 129 | 130 | ## Concepts 131 | 132 | **Prompt Bottle** 133 | 134 | The template of the prompt. It is a list of **Template Messages**. It can be rendered as python dict or JSON string. 135 | 136 | **Template Message** 137 | 138 | A message is either an OpenAI message dict, or a Jinja control block like `{% for ... %}` or `{% if ... %}`. 139 | 140 | If it is a text message, it will be rendered by Jinja firstly, like `{{ variable }}`. And then it will be rendered by `Multimodal Tag`. 141 | 142 | **Multimodal Tag** 143 | 144 | The tag inside a text can render the text message as multimodal parts. The tag looks like `https://your.image.url`, or using `pb_img_url("https://your.image.url")` function to get it. 145 | 146 | All the **Multimodal Tags** can be found in [prompt_bottle.tags.tags](./src/prompt_bottle/tags/tags.py). 147 | 148 | **Presets** 149 | 150 | Some common prompt templates are provided in [prompt_bottle.presets](./src/prompt_bottle/presets). 151 | -------------------------------------------------------------------------------- /examples/bottle.py: -------------------------------------------------------------------------------- 1 | from rich import print 2 | 3 | from prompt_bottle import PromptBottle, pb_img_url 4 | 5 | bottle = PromptBottle( 6 | [ 7 | { 8 | "role": "system", 9 | "content": "You are a helpful assistant in the domain of {{ domain }}", 10 | }, 11 | "{% for round in rounds %}", 12 | { 13 | "role": "user", 14 | "content": "Question: {{ round[0] }}", 15 | }, 16 | { 17 | "role": "assistant", 18 | "content": "Answer: {{ round[1] }}", 19 | }, 20 | "{% endfor %}", 21 | {"role": "user", "content": "Question: {{ final_question }}"}, 22 | ] 23 | ) 24 | 25 | prompt = bottle.render( 26 | domain="math", 27 | rounds=[ 28 | ("1+1", "2"), 29 | ( 30 | f"What is this picture? {pb_img_url('https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg')}", 31 | "This is an example image by Wikipedia", 32 | ), 33 | ], 34 | final_question="8*8", 35 | ) 36 | 37 | print(prompt) 38 | 39 | from openai import OpenAI 40 | 41 | client = OpenAI() 42 | 43 | response = client.chat.completions.create( 44 | model="gpt-4o-mini", 45 | messages=prompt, 46 | ) 47 | 48 | print(response.choices[0].message.content) 49 | -------------------------------------------------------------------------------- /examples/preset_simple_bottle.py: -------------------------------------------------------------------------------- 1 | from rich import print 2 | 3 | from prompt_bottle import pb_img_url, simple_bottle 4 | 5 | temp = simple_bottle( 6 | system="You are a helpful assistant {{ picture }}", 7 | user="What is the capital of {{ country }}? And what is the city in the picture? {{ picture }}", 8 | ) 9 | 10 | print( 11 | temp.render(country="France", picture=pb_img_url("https://example.com/image.jpg")) 12 | ) 13 | -------------------------------------------------------------------------------- /examples/roleplay.py: -------------------------------------------------------------------------------- 1 | # This example is written on dependency version: 2 | # pip install rich "stone-brick-toolkit[llm]~=0.5.0" "prompt-bottle~=0.1.3" 3 | from enum import Enum 4 | from typing import ( 5 | List, 6 | ) 7 | 8 | from openai import AsyncOpenAI 9 | from pydantic import BaseModel 10 | from rich import print 11 | from stone_brick.llm.error import GeneratedNotValid 12 | from stone_brick.llm.utils import oai_gen_with_retry_then_validate 13 | from stone_brick.parser import flat_xml_tags_from_text 14 | 15 | from prompt_bottle import PromptBottle 16 | 17 | 18 | def clean_multiline_string(str): 19 | return "\n".join([line.strip() for line in str.split("\n") if line.strip()]) 20 | 21 | 22 | class LogType(str, Enum): 23 | PLAYER_ACT = "Act" 24 | NPC_SPEECH = "Speech" 25 | NPC_PERFORMANCE = "Performance" 26 | VOICE_OVER = "VoiceOver" 27 | END_TURN = "EndTurn" 28 | 29 | 30 | class LogEntry(BaseModel): 31 | type: LogType 32 | role: str 33 | content: str 34 | 35 | def view_of_npc(self, player: str, npc: str): 36 | if self.role == player: 37 | return "user", self.content 38 | elif self.role == npc or self.type == LogType.VOICE_OVER: 39 | return ( 40 | "assistant", 41 | f"<{self.type.value}>{self.content}", 42 | ) 43 | else: 44 | return ( 45 | "system", 46 | f"{self.role}<{self.type.value}>{self.content}", 47 | ) 48 | 49 | 50 | npc_bottle = PromptBottle( 51 | [ 52 | { 53 | "role": "system", 54 | "content": clean_multiline_string( 55 | """ 56 | You are playing the role of {{npc}}. 57 | {{description}} 58 | """ 59 | ), 60 | }, 61 | { 62 | "role": "system", 63 | "content": clean_multiline_string( 64 | """ 65 | Output should be only one of following tags: 66 | ```xml 67 | 68 | Speech is what a character says. 69 | 70 | 71 | 72 | Performance is a description of the character's action, 73 | including their posture, gesture, facial expression, action, voice tone, etc. 74 | anything except what they say. 75 | 76 | 77 | 78 | Voice-over is used to describe the stage. 79 | It happens when a change of scene, a change of location, a change of time, etc. 80 | Or a change involves multiple characters. 81 | Or it can be a description of the environment, e.g. the weather, the time of day, etc. 82 | 83 | 84 | 85 | ``` 86 | You can use the types of tags that you used before, 87 | but you are not allowed to repeat the same content in any form. 88 | Actively use to describe the stage if it is needed for a change. 89 | When your turn is over, you should output . 90 | Your turn should no longer than 6 tags. 91 | """ 92 | ), 93 | }, 94 | "{% if rounds|length < examples|length %}", 95 | "{% for log in examples %}", 96 | { 97 | "role": "{{ log.view_of_npc(player, npc)[0] }}", # type: ignore 98 | "content": "{{ log.view_of_npc(player, npc)[1] }}", 99 | }, 100 | "{% endfor %}", 101 | "{% endif %}", 102 | "{% for log in rounds %}", 103 | { 104 | "role": "{{ log.view_of_npc(player, npc)[0] }}", # type: ignore 105 | "content": "{{ log.view_of_npc(player, npc)[1] }}", 106 | }, 107 | "{% endfor %}", 108 | ] 109 | ) 110 | 111 | 112 | PLAYER = "Traveler" 113 | NPC = "Paimon" 114 | DESC = """Paimon has a petite body, giving her the look of a fairy. She has thick white hair cropped just above her shoulders, dark purple eyes, and light skin. In some official art, the tip of one of her front hair locks fades to black. 115 | She wears a long-sleeved white jumper and a night-blue cape flecked with stars, and white stockings with white boots. Rose-gold embroidery and shapes are attached to her jumper, boots, and sleeves. 116 | Paimon's accessories are a dark blue hairpin, almost black, and a rose-gold tiara that levitates above her head like a halo. 117 | Paimon serves as the Traveler's guide and companion throughout their journey in Teyvat. While not directly involved in combat, she provides crucial information, hints, and emotional support. 118 | Paimon's personality: 119 | - Cute and Joyful: Her design and voice contribute to a generally adorable and cheerful demeanor. 120 | - Curious and Inquisitive: She frequently asks questions and expresses wonder about the world around her. 121 | - Kind and Supportive: She genuinely cares for the Traveler and offers encouragement and guidance. 122 | - Greedy: Paimon readily shows excitement at the prospect of treasure or valuable items. 123 | - Blunt and Candid: She doesn't hesitate to express her dislikes, sometimes using nicknames for people she doesn't like. 124 | - Fearful: She's easily scared by monsters and avoids combat. 125 | - Annoying (to some): Her constant chatter and occasional self-centeredness can be frustrating to some players. The nickname "Emergency Food" frequently used by the Traveler, highlights this aspect of her character. 126 | """ 127 | 128 | 129 | examples = [ 130 | LogEntry(role=PLAYER, type=LogType.PLAYER_ACT, content="How are you?"), 131 | LogEntry( 132 | role=NPC, 133 | type=LogType.NPC_SPEECH, 134 | content=clean_multiline_string( 135 | f""" 136 | Heehee! I'm doing great, {PLAYER}! Just whizzing around, you know? So much to see! 137 | Did you see that amazing sparkly thing? It was blue and swirly and... 138 | oh! Where were we?""" 139 | ), 140 | ), 141 | LogEntry( 142 | role=NPC, 143 | type=LogType.NPC_PERFORMANCE, 144 | content="I tilts my head, my violet eyes sparkling with mischief.", 145 | ), 146 | LogEntry( 147 | role=NPC, 148 | type=LogType.NPC_SPEECH, 149 | content=f"Oh right! How are you, {PLAYER}?", 150 | ), 151 | LogEntry(role=PLAYER, type=LogType.PLAYER_ACT, content="I'm doing ok."), 152 | LogEntry( 153 | role=NPC, 154 | type=LogType.NPC_PERFORMANCE, 155 | content="Hovering slightly closer, my tiny starry cape fluttering.", 156 | ), 157 | LogEntry( 158 | role="", 159 | type=LogType.VOICE_OVER, 160 | content="Paimon smiles warmly, her violet eyes crinkling with happiness.", 161 | ), 162 | LogEntry( 163 | role=NPC, 164 | type=LogType.NPC_SPEECH, 165 | content="Just okay? Paimon thinks we need an adventure to cheer you up! Maybe find some treasure? Or some yummy food?", 166 | ), 167 | LogEntry( 168 | role=NPC, 169 | type=LogType.NPC_PERFORMANCE, 170 | content="I nod my head, rubbing my hands together excitedly.", 171 | ), 172 | ] 173 | 174 | 175 | async def try_generate( 176 | oai: AsyncOpenAI, 177 | model: str, 178 | npc: str, 179 | player: str, 180 | description: str, 181 | examples: List[LogEntry], 182 | rounds: List[LogEntry], 183 | ): 184 | prompt = npc_bottle.render( 185 | npc=npc, 186 | player=player, 187 | description=description, 188 | examples=examples, 189 | rounds=rounds, 190 | ) 191 | 192 | def validator(text: str): 193 | tags = flat_xml_tags_from_text(text, [tag.value for tag in LogType]) 194 | for item in tags: 195 | if isinstance(item, tuple): 196 | return LogType(item[0]), item[1] 197 | raise GeneratedNotValid() 198 | 199 | parsed = await oai_gen_with_retry_then_validate( 200 | oai_client=oai, 201 | model=model, 202 | prompt=prompt, 203 | generate_kwargs={"temperature": 0.8}, 204 | validator=validator, 205 | ) 206 | return parsed 207 | 208 | 209 | async def main(oai: AsyncOpenAI, model: str): 210 | rounds = [] 211 | while True: 212 | user_input: str = input("You: ") 213 | rounds.append( 214 | LogEntry(role=PLAYER, type=LogType.PLAYER_ACT, content=user_input) 215 | ) 216 | while True: 217 | log_type, content = await try_generate( 218 | oai, model, NPC, PLAYER, DESC, examples, rounds 219 | ) 220 | if log_type == LogType.END_TURN: 221 | break 222 | elif log_type == LogType.PLAYER_ACT: 223 | print("Generated player act, ignore") 224 | continue 225 | elif log_type == LogType.VOICE_OVER: 226 | rounds.append(LogEntry(role="", type=log_type, content=content)) 227 | print(f"Paimon: ({log_type.value}) {content}") 228 | else: 229 | rounds.append(LogEntry(role=NPC, type=log_type, content=content)) 230 | print(f"Paimon: ({log_type.value}) {content}") 231 | 232 | 233 | if __name__ == "__main__": 234 | import asyncio 235 | 236 | oai = AsyncOpenAI() 237 | asyncio.run(main(oai, "gpt-4o-mini")) 238 | -------------------------------------------------------------------------------- /pdm.lock: -------------------------------------------------------------------------------- 1 | # This file is @generated by PDM. 2 | # It is not intended for manual editing. 3 | 4 | [metadata] 5 | groups = ["default", "dev"] 6 | strategy = ["inherit_metadata"] 7 | lock_version = "4.5.0" 8 | content_hash = "sha256:233b5ce4d9f6a5b96a5c97515beb4e58b177d62e632af49b03dbbcab638abc96" 9 | 10 | [[metadata.targets]] 11 | requires_python = ">=3.9" 12 | 13 | [[package]] 14 | name = "annotated-types" 15 | version = "0.7.0" 16 | requires_python = ">=3.8" 17 | summary = "Reusable constraint types to use with typing.Annotated" 18 | groups = ["default"] 19 | dependencies = [ 20 | "typing-extensions>=4.0.0; python_version < \"3.9\"", 21 | ] 22 | files = [ 23 | {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, 24 | {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, 25 | ] 26 | 27 | [[package]] 28 | name = "anyio" 29 | version = "4.6.2.post1" 30 | requires_python = ">=3.9" 31 | summary = "High level compatibility layer for multiple asynchronous event loop implementations" 32 | groups = ["default"] 33 | dependencies = [ 34 | "exceptiongroup>=1.0.2; python_version < \"3.11\"", 35 | "idna>=2.8", 36 | "sniffio>=1.1", 37 | "typing-extensions>=4.1; python_version < \"3.11\"", 38 | ] 39 | files = [ 40 | {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, 41 | {file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"}, 42 | ] 43 | 44 | [[package]] 45 | name = "certifi" 46 | version = "2024.8.30" 47 | requires_python = ">=3.6" 48 | summary = "Python package for providing Mozilla's CA Bundle." 49 | groups = ["default"] 50 | files = [ 51 | {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, 52 | {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, 53 | ] 54 | 55 | [[package]] 56 | name = "colorama" 57 | version = "0.4.6" 58 | requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 59 | summary = "Cross-platform colored terminal text." 60 | groups = ["default"] 61 | marker = "platform_system == \"Windows\"" 62 | files = [ 63 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 64 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 65 | ] 66 | 67 | [[package]] 68 | name = "distro" 69 | version = "1.9.0" 70 | requires_python = ">=3.6" 71 | summary = "Distro - an OS platform information API" 72 | groups = ["default"] 73 | files = [ 74 | {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, 75 | {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, 76 | ] 77 | 78 | [[package]] 79 | name = "exceptiongroup" 80 | version = "1.2.2" 81 | requires_python = ">=3.7" 82 | summary = "Backport of PEP 654 (exception groups)" 83 | groups = ["default"] 84 | marker = "python_version < \"3.11\"" 85 | files = [ 86 | {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, 87 | {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, 88 | ] 89 | 90 | [[package]] 91 | name = "h11" 92 | version = "0.14.0" 93 | requires_python = ">=3.7" 94 | summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 95 | groups = ["default"] 96 | dependencies = [ 97 | "typing-extensions; python_version < \"3.8\"", 98 | ] 99 | files = [ 100 | {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, 101 | {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, 102 | ] 103 | 104 | [[package]] 105 | name = "httpcore" 106 | version = "1.0.7" 107 | requires_python = ">=3.8" 108 | summary = "A minimal low-level HTTP client." 109 | groups = ["default"] 110 | dependencies = [ 111 | "certifi", 112 | "h11<0.15,>=0.13", 113 | ] 114 | files = [ 115 | {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, 116 | {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, 117 | ] 118 | 119 | [[package]] 120 | name = "httpx" 121 | version = "0.27.2" 122 | requires_python = ">=3.8" 123 | summary = "The next generation HTTP client." 124 | groups = ["default"] 125 | dependencies = [ 126 | "anyio", 127 | "certifi", 128 | "httpcore==1.*", 129 | "idna", 130 | "sniffio", 131 | ] 132 | files = [ 133 | {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, 134 | {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, 135 | ] 136 | 137 | [[package]] 138 | name = "idna" 139 | version = "3.10" 140 | requires_python = ">=3.6" 141 | summary = "Internationalized Domain Names in Applications (IDNA)" 142 | groups = ["default"] 143 | files = [ 144 | {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, 145 | {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, 146 | ] 147 | 148 | [[package]] 149 | name = "jinja2" 150 | version = "3.1.4" 151 | requires_python = ">=3.7" 152 | summary = "A very fast and expressive template engine." 153 | groups = ["default"] 154 | dependencies = [ 155 | "MarkupSafe>=2.0", 156 | ] 157 | files = [ 158 | {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, 159 | {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, 160 | ] 161 | 162 | [[package]] 163 | name = "jiter" 164 | version = "0.7.1" 165 | requires_python = ">=3.8" 166 | summary = "Fast iterable JSON parser." 167 | groups = ["default"] 168 | files = [ 169 | {file = "jiter-0.7.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:262e96d06696b673fad6f257e6a0abb6e873dc22818ca0e0600f4a1189eb334f"}, 170 | {file = "jiter-0.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be6de02939aac5be97eb437f45cfd279b1dc9de358b13ea6e040e63a3221c40d"}, 171 | {file = "jiter-0.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935f10b802bc1ce2b2f61843e498c7720aa7f4e4bb7797aa8121eab017293c3d"}, 172 | {file = "jiter-0.7.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9cd3cccccabf5064e4bb3099c87bf67db94f805c1e62d1aefd2b7476e90e0ee2"}, 173 | {file = "jiter-0.7.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4aa919ebfc5f7b027cc368fe3964c0015e1963b92e1db382419dadb098a05192"}, 174 | {file = "jiter-0.7.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ae2d01e82c94491ce4d6f461a837f63b6c4e6dd5bb082553a70c509034ff3d4"}, 175 | {file = "jiter-0.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f9568cd66dbbdab67ae1b4c99f3f7da1228c5682d65913e3f5f95586b3cb9a9"}, 176 | {file = "jiter-0.7.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9ecbf4e20ec2c26512736284dc1a3f8ed79b6ca7188e3b99032757ad48db97dc"}, 177 | {file = "jiter-0.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b1a0508fddc70ce00b872e463b387d49308ef02b0787992ca471c8d4ba1c0fa1"}, 178 | {file = "jiter-0.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f84c9996664c460f24213ff1e5881530abd8fafd82058d39af3682d5fd2d6316"}, 179 | {file = "jiter-0.7.1-cp310-none-win32.whl", hash = "sha256:c915e1a1960976ba4dfe06551ea87063b2d5b4d30759012210099e712a414d9f"}, 180 | {file = "jiter-0.7.1-cp310-none-win_amd64.whl", hash = "sha256:75bf3b7fdc5c0faa6ffffcf8028a1f974d126bac86d96490d1b51b3210aa0f3f"}, 181 | {file = "jiter-0.7.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ad04a23a91f3d10d69d6c87a5f4471b61c2c5cd6e112e85136594a02043f462c"}, 182 | {file = "jiter-0.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e47a554de88dff701226bb5722b7f1b6bccd0b98f1748459b7e56acac2707a5"}, 183 | {file = "jiter-0.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e44fff69c814a2e96a20b4ecee3e2365e9b15cf5fe4e00869d18396daa91dab"}, 184 | {file = "jiter-0.7.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df0a1d05081541b45743c965436f8b5a1048d6fd726e4a030113a2699a6046ea"}, 185 | {file = "jiter-0.7.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f22cf8f236a645cb6d8ffe2a64edb5d2b66fb148bf7c75eea0cb36d17014a7bc"}, 186 | {file = "jiter-0.7.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8589f50b728ea4bf22e0632eefa125c8aa9c38ed202a5ee6ca371f05eeb3ff"}, 187 | {file = "jiter-0.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f20de711224f2ca2dbb166a8d512f6ff48c9c38cc06b51f796520eb4722cc2ce"}, 188 | {file = "jiter-0.7.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8a9803396032117b85ec8cbf008a54590644a062fedd0425cbdb95e4b2b60479"}, 189 | {file = "jiter-0.7.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3d8bae77c82741032e9d89a4026479061aba6e646de3bf5f2fc1ae2bbd9d06e0"}, 190 | {file = "jiter-0.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3dc9939e576bbc68c813fc82f6620353ed68c194c7bcf3d58dc822591ec12490"}, 191 | {file = "jiter-0.7.1-cp311-none-win32.whl", hash = "sha256:f7605d24cd6fab156ec89e7924578e21604feee9c4f1e9da34d8b67f63e54892"}, 192 | {file = "jiter-0.7.1-cp311-none-win_amd64.whl", hash = "sha256:f3ea649e7751a1a29ea5ecc03c4ada0a833846c59c6da75d747899f9b48b7282"}, 193 | {file = "jiter-0.7.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ad36a1155cbd92e7a084a568f7dc6023497df781adf2390c345dd77a120905ca"}, 194 | {file = "jiter-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7ba52e6aaed2dc5c81a3d9b5e4ab95b039c4592c66ac973879ba57c3506492bb"}, 195 | {file = "jiter-0.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b7de0b6f6728b678540c7927587e23f715284596724be203af952418acb8a2d"}, 196 | {file = "jiter-0.7.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9463b62bd53c2fb85529c700c6a3beb2ee54fde8bef714b150601616dcb184a6"}, 197 | {file = "jiter-0.7.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:627164ec01d28af56e1f549da84caf0fe06da3880ebc7b7ee1ca15df106ae172"}, 198 | {file = "jiter-0.7.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25d0e5bf64e368b0aa9e0a559c3ab2f9b67e35fe7269e8a0d81f48bbd10e8963"}, 199 | {file = "jiter-0.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c244261306f08f8008b3087059601997016549cb8bb23cf4317a4827f07b7d74"}, 200 | {file = "jiter-0.7.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7ded4e4b75b68b843b7cea5cd7c55f738c20e1394c68c2cb10adb655526c5f1b"}, 201 | {file = "jiter-0.7.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:80dae4f1889b9d09e5f4de6b58c490d9c8ce7730e35e0b8643ab62b1538f095c"}, 202 | {file = "jiter-0.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5970cf8ec943b51bce7f4b98d2e1ed3ada170c2a789e2db3cb484486591a176a"}, 203 | {file = "jiter-0.7.1-cp312-none-win32.whl", hash = "sha256:701d90220d6ecb3125d46853c8ca8a5bc158de8c49af60fd706475a49fee157e"}, 204 | {file = "jiter-0.7.1-cp312-none-win_amd64.whl", hash = "sha256:7824c3ecf9ecf3321c37f4e4d4411aad49c666ee5bc2a937071bdd80917e4533"}, 205 | {file = "jiter-0.7.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:097676a37778ba3c80cb53f34abd6943ceb0848263c21bf423ae98b090f6c6ba"}, 206 | {file = "jiter-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3298af506d4271257c0a8f48668b0f47048d69351675dd8500f22420d4eec378"}, 207 | {file = "jiter-0.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12fd88cfe6067e2199964839c19bd2b422ca3fd792949b8f44bb8a4e7d21946a"}, 208 | {file = "jiter-0.7.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dacca921efcd21939123c8ea8883a54b9fa7f6545c8019ffcf4f762985b6d0c8"}, 209 | {file = "jiter-0.7.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de3674a5fe1f6713a746d25ad9c32cd32fadc824e64b9d6159b3b34fd9134143"}, 210 | {file = "jiter-0.7.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65df9dbae6d67e0788a05b4bad5706ad40f6f911e0137eb416b9eead6ba6f044"}, 211 | {file = "jiter-0.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ba9a358d59a0a55cccaa4957e6ae10b1a25ffdabda863c0343c51817610501d"}, 212 | {file = "jiter-0.7.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:576eb0f0c6207e9ede2b11ec01d9c2182973986514f9c60bc3b3b5d5798c8f50"}, 213 | {file = "jiter-0.7.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:e550e29cdf3577d2c970a18f3959e6b8646fd60ef1b0507e5947dc73703b5627"}, 214 | {file = "jiter-0.7.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:81d968dbf3ce0db2e0e4dec6b0a0d5d94f846ee84caf779b07cab49f5325ae43"}, 215 | {file = "jiter-0.7.1-cp313-none-win32.whl", hash = "sha256:f892e547e6e79a1506eb571a676cf2f480a4533675f834e9ae98de84f9b941ac"}, 216 | {file = "jiter-0.7.1-cp313-none-win_amd64.whl", hash = "sha256:0302f0940b1455b2a7fb0409b8d5b31183db70d2b07fd177906d83bf941385d1"}, 217 | {file = "jiter-0.7.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8f212eeacc7203256f526f550d105d8efa24605828382cd7d296b703181ff11d"}, 218 | {file = "jiter-0.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d9e247079d88c00e75e297e6cb3a18a039ebcd79fefc43be9ba4eb7fb43eb726"}, 219 | {file = "jiter-0.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0aacaa56360139c53dcf352992b0331f4057a0373bbffd43f64ba0c32d2d155"}, 220 | {file = "jiter-0.7.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bc1b55314ca97dbb6c48d9144323896e9c1a25d41c65bcb9550b3e0c270ca560"}, 221 | {file = "jiter-0.7.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f281aae41b47e90deb70e7386558e877a8e62e1693e0086f37d015fa1c102289"}, 222 | {file = "jiter-0.7.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:93c20d2730a84d43f7c0b6fb2579dc54335db742a59cf9776d0b80e99d587382"}, 223 | {file = "jiter-0.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e81ccccd8069110e150613496deafa10da2f6ff322a707cbec2b0d52a87b9671"}, 224 | {file = "jiter-0.7.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a7d5e85766eff4c9be481d77e2226b4c259999cb6862ccac5ef6621d3c8dcce"}, 225 | {file = "jiter-0.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f52ce5799df5b6975439ecb16b1e879d7655e1685b6e3758c9b1b97696313bfb"}, 226 | {file = "jiter-0.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0c91a0304373fdf97d56f88356a010bba442e6d995eb7773cbe32885b71cdd8"}, 227 | {file = "jiter-0.7.1-cp39-none-win32.whl", hash = "sha256:5c08adf93e41ce2755970e8aa95262298afe2bf58897fb9653c47cd93c3c6cdc"}, 228 | {file = "jiter-0.7.1-cp39-none-win_amd64.whl", hash = "sha256:6592f4067c74176e5f369228fb2995ed01400c9e8e1225fb73417183a5e635f0"}, 229 | {file = "jiter-0.7.1.tar.gz", hash = "sha256:448cf4f74f7363c34cdef26214da527e8eeffd88ba06d0b80b485ad0667baf5d"}, 230 | ] 231 | 232 | [[package]] 233 | name = "markdown-it-py" 234 | version = "3.0.0" 235 | requires_python = ">=3.8" 236 | summary = "Python port of markdown-it. Markdown parsing, done right!" 237 | groups = ["dev"] 238 | dependencies = [ 239 | "mdurl~=0.1", 240 | ] 241 | files = [ 242 | {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, 243 | {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, 244 | ] 245 | 246 | [[package]] 247 | name = "markupsafe" 248 | version = "3.0.2" 249 | requires_python = ">=3.9" 250 | summary = "Safely add untrusted strings to HTML/XML markup." 251 | groups = ["default"] 252 | files = [ 253 | {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, 254 | {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, 255 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, 256 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, 257 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, 258 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, 259 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, 260 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, 261 | {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, 262 | {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, 263 | {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, 264 | {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, 265 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, 266 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, 267 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, 268 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, 269 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, 270 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, 271 | {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, 272 | {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, 273 | {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, 274 | {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, 275 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, 276 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, 277 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, 278 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, 279 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, 280 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, 281 | {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, 282 | {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, 283 | {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, 284 | {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, 285 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, 286 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, 287 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, 288 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, 289 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, 290 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, 291 | {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, 292 | {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, 293 | {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, 294 | {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, 295 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, 296 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, 297 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, 298 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, 299 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, 300 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, 301 | {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, 302 | {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, 303 | {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, 304 | {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, 305 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, 306 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, 307 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, 308 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, 309 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, 310 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, 311 | {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, 312 | {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, 313 | {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, 314 | ] 315 | 316 | [[package]] 317 | name = "mdurl" 318 | version = "0.1.2" 319 | requires_python = ">=3.7" 320 | summary = "Markdown URL utilities" 321 | groups = ["dev"] 322 | files = [ 323 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, 324 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, 325 | ] 326 | 327 | [[package]] 328 | name = "openai" 329 | version = "1.55.1" 330 | requires_python = ">=3.8" 331 | summary = "The official Python library for the openai API" 332 | groups = ["default"] 333 | dependencies = [ 334 | "anyio<5,>=3.5.0", 335 | "distro<2,>=1.7.0", 336 | "httpx<1,>=0.23.0", 337 | "jiter<1,>=0.4.0", 338 | "pydantic<3,>=1.9.0", 339 | "sniffio", 340 | "tqdm>4", 341 | "typing-extensions<5,>=4.11", 342 | ] 343 | files = [ 344 | {file = "openai-1.55.1-py3-none-any.whl", hash = "sha256:d10d96a4f9dc5f05d38dea389119ec8dcd24bc9698293c8357253c601b4a77a5"}, 345 | {file = "openai-1.55.1.tar.gz", hash = "sha256:471324321e7739214f16a544e801947a046d3c5d516fae8719a317234e4968d3"}, 346 | ] 347 | 348 | [[package]] 349 | name = "pydantic" 350 | version = "2.9.2" 351 | requires_python = ">=3.8" 352 | summary = "Data validation using Python type hints" 353 | groups = ["default"] 354 | dependencies = [ 355 | "annotated-types>=0.6.0", 356 | "pydantic-core==2.23.4", 357 | "typing-extensions>=4.12.2; python_version >= \"3.13\"", 358 | "typing-extensions>=4.6.1; python_version < \"3.13\"", 359 | ] 360 | files = [ 361 | {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, 362 | {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, 363 | ] 364 | 365 | [[package]] 366 | name = "pydantic-core" 367 | version = "2.23.4" 368 | requires_python = ">=3.8" 369 | summary = "Core functionality for Pydantic validation and serialization" 370 | groups = ["default"] 371 | dependencies = [ 372 | "typing-extensions!=4.7.0,>=4.6.0", 373 | ] 374 | files = [ 375 | {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, 376 | {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, 377 | {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, 378 | {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, 379 | {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, 380 | {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, 381 | {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, 382 | {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, 383 | {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, 384 | {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, 385 | {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, 386 | {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, 387 | {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, 388 | {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, 389 | {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, 390 | {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, 391 | {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, 392 | {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, 393 | {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, 394 | {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, 395 | {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, 396 | {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, 397 | {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, 398 | {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, 399 | {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, 400 | {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, 401 | {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, 402 | {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, 403 | {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, 404 | {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, 405 | {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, 406 | {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, 407 | {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, 408 | {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, 409 | {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, 410 | {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, 411 | {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, 412 | {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, 413 | {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, 414 | {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, 415 | {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, 416 | {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, 417 | {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, 418 | {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, 419 | {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, 420 | {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, 421 | {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, 422 | {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, 423 | {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, 424 | {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, 425 | {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, 426 | {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, 427 | {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, 428 | {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, 429 | {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, 430 | {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, 431 | {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, 432 | {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, 433 | {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, 434 | {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, 435 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, 436 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, 437 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, 438 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, 439 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, 440 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, 441 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, 442 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, 443 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, 444 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, 445 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, 446 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, 447 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, 448 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, 449 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, 450 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, 451 | {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, 452 | ] 453 | 454 | [[package]] 455 | name = "pygments" 456 | version = "2.18.0" 457 | requires_python = ">=3.8" 458 | summary = "Pygments is a syntax highlighting package written in Python." 459 | groups = ["dev"] 460 | files = [ 461 | {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, 462 | {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, 463 | ] 464 | 465 | [[package]] 466 | name = "pyyaml" 467 | version = "6.0.2" 468 | requires_python = ">=3.8" 469 | summary = "YAML parser and emitter for Python" 470 | groups = ["default"] 471 | files = [ 472 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, 473 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, 474 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, 475 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, 476 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, 477 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, 478 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, 479 | {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, 480 | {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, 481 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, 482 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, 483 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, 484 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, 485 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, 486 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, 487 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, 488 | {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, 489 | {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, 490 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, 491 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, 492 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, 493 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, 494 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, 495 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, 496 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, 497 | {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, 498 | {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, 499 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, 500 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, 501 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, 502 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, 503 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, 504 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, 505 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, 506 | {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, 507 | {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, 508 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, 509 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, 510 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, 511 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, 512 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, 513 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, 514 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, 515 | {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, 516 | {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, 517 | {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, 518 | ] 519 | 520 | [[package]] 521 | name = "rich" 522 | version = "13.9.4" 523 | requires_python = ">=3.8.0" 524 | summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 525 | groups = ["dev"] 526 | dependencies = [ 527 | "markdown-it-py>=2.2.0", 528 | "pygments<3.0.0,>=2.13.0", 529 | "typing-extensions<5.0,>=4.0.0; python_version < \"3.11\"", 530 | ] 531 | files = [ 532 | {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, 533 | {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, 534 | ] 535 | 536 | [[package]] 537 | name = "ruff" 538 | version = "0.8.1" 539 | requires_python = ">=3.7" 540 | summary = "An extremely fast Python linter and code formatter, written in Rust." 541 | groups = ["dev"] 542 | files = [ 543 | {file = "ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"}, 544 | {file = "ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087"}, 545 | {file = "ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209"}, 546 | {file = "ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871"}, 547 | {file = "ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1"}, 548 | {file = "ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5"}, 549 | {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d"}, 550 | {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26"}, 551 | {file = "ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1"}, 552 | {file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"}, 553 | {file = "ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa"}, 554 | {file = "ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540"}, 555 | {file = "ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9"}, 556 | {file = "ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5"}, 557 | {file = "ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790"}, 558 | {file = "ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6"}, 559 | {file = "ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737"}, 560 | {file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"}, 561 | ] 562 | 563 | [[package]] 564 | name = "sniffio" 565 | version = "1.3.1" 566 | requires_python = ">=3.7" 567 | summary = "Sniff out which async library your code is running under" 568 | groups = ["default"] 569 | files = [ 570 | {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, 571 | {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, 572 | ] 573 | 574 | [[package]] 575 | name = "stone-brick-toolkit" 576 | version = "0.6.1" 577 | requires_python = ">=3.9" 578 | summary = "Stone Brick is a toolkit providing some commonly used utilities." 579 | groups = ["default"] 580 | files = [ 581 | {file = "stone_brick_toolkit-0.6.1-py3-none-any.whl", hash = "sha256:cfb759c14c15ce44efd96a15c7883e40a7e112d663a6362062e3a7587f7d879b"}, 582 | {file = "stone_brick_toolkit-0.6.1.tar.gz", hash = "sha256:bd1dd12ee120c0f2f4953b32370db2f12d0e94fb0acd7a9600f9ca5bbc6a43df"}, 583 | ] 584 | 585 | [[package]] 586 | name = "tqdm" 587 | version = "4.67.0" 588 | requires_python = ">=3.7" 589 | summary = "Fast, Extensible Progress Meter" 590 | groups = ["default"] 591 | dependencies = [ 592 | "colorama; platform_system == \"Windows\"", 593 | ] 594 | files = [ 595 | {file = "tqdm-4.67.0-py3-none-any.whl", hash = "sha256:0cd8af9d56911acab92182e88d763100d4788bdf421d251616040cc4d44863be"}, 596 | {file = "tqdm-4.67.0.tar.gz", hash = "sha256:fe5a6f95e6fe0b9755e9469b77b9c3cf850048224ecaa8293d7d2d31f97d869a"}, 597 | ] 598 | 599 | [[package]] 600 | name = "typing-extensions" 601 | version = "4.12.2" 602 | requires_python = ">=3.8" 603 | summary = "Backported and Experimental Type Hints for Python 3.8+" 604 | groups = ["default", "dev"] 605 | files = [ 606 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 607 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 608 | ] 609 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "prompt_bottle" 3 | version = "0.1.5" 4 | description = "LLM-targeted template engine, built upon Jinja" 5 | authors = [ 6 | {name = "Yanli",email = "mail@yanli.one"}, 7 | ] 8 | dependencies = [ 9 | "openai>=1.51.2", 10 | "jinja2>=3.1.4", 11 | "PyYAML>=6.0.2", 12 | "stone-brick-toolkit>=0.6.1,<1.0.0" 13 | ] 14 | requires-python = ">=3.9" 15 | readme = "README.md" 16 | license = {text = "MIT"} 17 | [project.urls] 18 | Repository = "https://github.com/BeautyyuYanli/Prompt-Bottle" 19 | 20 | [dependency-groups] 21 | dev = [ 22 | "pytest>=8.3.3", 23 | "rich>=13.9.4" 24 | ] 25 | 26 | 27 | [build-system] 28 | requires = ["pdm-backend"] 29 | build-backend = "pdm.backend" 30 | 31 | [tool.pdm] 32 | distribution = true 33 | 34 | [tool.pdm.dev-dependencies] 35 | dev = [ 36 | "rich>=13.9.4", 37 | "ruff>=0.8.1", 38 | ] 39 | [tool.pyright] 40 | venvPath = ".venv" 41 | 42 | [tool.ruff] 43 | target-version = "py39" 44 | exclude = [".venv"] 45 | [tool.ruff.lint] 46 | select = ["E", "F", "G", "B", "I", "SIM", "TID", "PL", "RUF"] 47 | ignore = [ 48 | "RUF001", # ',' vs. ',' 49 | "RUF003", # Ambiguous unicode character comment 50 | "E501", # Line too long 51 | "E402", # Module level import not at top of file 52 | "PLR0911", # Too many return statements 53 | "PLR0912", # Too many branches 54 | "PLR0913", # Too many arguments in function definition 55 | "PLR0915", # Too many statements 56 | "SIM105", # Use `contextlib.suppress(Exception)` instead of `try`-`except`-`pass` 57 | "SIM102", # Use a single `if` statement instead of nested `if` statements 58 | ] 59 | 60 | [tool.ruff.lint.isort] 61 | known-first-party = ["prompt_bottle"] 62 | 63 | 64 | [tool.pdm.scripts] 65 | fix = { shell = "ruff check --fix && ruff format" } 66 | check = { shell = 'ruff check && ruff format --check' } -------------------------------------------------------------------------------- /src/prompt_bottle/__init__.py: -------------------------------------------------------------------------------- 1 | from prompt_bottle.bottle import PromptBottle 2 | from prompt_bottle.presets.simple import simple_bottle 3 | from prompt_bottle.tags.tags import ( 4 | PBTag, 5 | pb_audio, 6 | pb_img_url, 7 | pb_mp3_audio, 8 | pb_tag, 9 | pb_wav_audio, 10 | ) 11 | 12 | __all__ = [ 13 | "PBTag", 14 | "PromptBottle", 15 | "pb_audio", 16 | "pb_img_url", 17 | "pb_mp3_audio", 18 | "pb_tag", 19 | "pb_wav_audio", 20 | "simple_bottle", 21 | ] 22 | -------------------------------------------------------------------------------- /src/prompt_bottle/bottle.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | from typing import ( 4 | Iterable, 5 | List, 6 | Sequence, 7 | Union, 8 | cast, 9 | ) 10 | 11 | import yaml 12 | from jinja2 import Template 13 | from openai.types.chat import ( 14 | ChatCompletionAssistantMessageParam, 15 | ChatCompletionContentPartParam, 16 | ChatCompletionContentPartTextParam, 17 | ChatCompletionMessageParam, 18 | ChatCompletionSystemMessageParam, 19 | ChatCompletionUserMessageParam, 20 | ) 21 | from openai.types.chat.chat_completion_assistant_message_param import ( 22 | ContentArrayOfContentPart, 23 | ) 24 | from stone_brick.parser.xml import flat_xml_tags_from_text 25 | 26 | from prompt_bottle.tags.convert_to import PB_TAG_TO_OPENAI, to_text_part 27 | from prompt_bottle.tags.tags import PBTag 28 | from prompt_bottle.utils import check_type 29 | 30 | # from typeguard import check_type 31 | 32 | ALL_PART_PARAM = Union[ChatCompletionContentPartParam, ContentArrayOfContentPart] 33 | 34 | PLACEHOLDER = "PROMPT_BOTTLE_PLACEHOLDER" 35 | 36 | 37 | def _encapsulate(value): 38 | """Surround all {{...}} patterns to be {{...}}""" 39 | if isinstance(value, str): 40 | # Find all {{...}} patterns and wrap them with placeholders 41 | pattern = r"(\{\{.*?\}\})" 42 | value = re.sub(pattern, f"<{PLACEHOLDER}>\\1", value) 43 | return value 44 | elif isinstance(value, dict): 45 | return {k: _encapsulate(v) for k, v in value.items()} 46 | elif isinstance(value, list): 47 | return [_encapsulate(v) for v in value] 48 | else: 49 | return value 50 | 51 | 52 | def _decapsulate(value: str): 53 | """Get all any string patterns, remove the placeholders, 54 | and dump things in it as a json string""" 55 | pattern = f"<{PLACEHOLDER}>(.*?)" 56 | 57 | # Find all matches and replace them with their JSON-decoded content 58 | def replace_func(match: re.Match): 59 | content = match.group(1) 60 | # Encode the content as a JSON string 61 | return json.dumps(content, ensure_ascii=False)[1:-1] 62 | 63 | return re.sub(pattern, replace_func, value, flags=re.DOTALL) 64 | 65 | 66 | def _convert_controlled_struct( 67 | template: Sequence[Union[ChatCompletionMessageParam, str]], 68 | ) -> str: 69 | """Convert the bottle template to a json string, so that it can be rendered by jinja2""" 70 | string_list: List[str] = [] 71 | for message in template: 72 | if isinstance(message, str): 73 | if re.match(r"^\s*\{\{.*\}\}\s*$", message.strip()): 74 | string_list.append(message + ",") 75 | elif re.match(r"^\s*\{\%.*\%\}\s*$", message.strip()): 76 | string_list.append(message) 77 | else: 78 | raise ValueError( 79 | f"Unknown template string: {message} \nThe string in list can only be {{{{ var }}}} or {{% expression %}}" 80 | ) 81 | else: 82 | string_list.append( 83 | json.dumps(_encapsulate(message), ensure_ascii=False) + "," 84 | ) 85 | return "[" + "".join(string_list) + "]" 86 | 87 | 88 | class PromptBottle: 89 | template: str 90 | 91 | def __init__( 92 | self, 93 | template: List[Union[ChatCompletionMessageParam, str]], 94 | ): 95 | if isinstance(template, str): 96 | self.template = template 97 | else: 98 | self.template = _convert_controlled_struct(template) 99 | 100 | def render(self, **kwargs) -> List[ChatCompletionMessageParam]: 101 | return render_string(self.template, **kwargs) 102 | 103 | def render_as_json(self, **kwargs) -> str: 104 | return json.dumps(self.render(**kwargs)) 105 | 106 | 107 | def render_text( 108 | text: str, jinja_render: bool = False, **kwargs 109 | ) -> List[ChatCompletionContentPartParam]: 110 | if jinja_render: 111 | text = Template(text).render(**kwargs) 112 | parts: List[ChatCompletionContentPartParam] = [] 113 | 114 | tags = flat_xml_tags_from_text(text, [tag.value for tag in PBTag]) 115 | for tag in tags: 116 | if isinstance(tag, tuple): 117 | name, content = tag 118 | parts.append(PB_TAG_TO_OPENAI[PBTag(name)](content)) 119 | else: 120 | parts.append(to_text_part(tag)) 121 | 122 | return parts 123 | 124 | 125 | def render_string(template: str, **kwargs) -> List[ChatCompletionMessageParam]: 126 | expanded = Template(template).render(**kwargs) 127 | json_expanded = check_type( 128 | yaml.safe_load(_decapsulate(expanded)), List[ChatCompletionMessageParam] 129 | ) 130 | return render_struct(json_expanded, **kwargs) 131 | 132 | 133 | def render_struct( 134 | template: Sequence[ChatCompletionMessageParam], 135 | **kwargs, 136 | ) -> List[ChatCompletionMessageParam]: 137 | def render_str_or_parts( 138 | source: Union[str, Iterable[ALL_PART_PARAM]], 139 | ): 140 | if isinstance(source, str): 141 | return render_text(source, **kwargs) 142 | new_source: List[ALL_PART_PARAM] = [] 143 | for part in source: 144 | if part["type"] == "text": 145 | part = cast(ChatCompletionContentPartTextParam, part) 146 | new_source.extend(render_text(part["text"], **kwargs)) 147 | else: 148 | new_source.append(part) 149 | return new_source 150 | 151 | def render_user_message(message: ChatCompletionUserMessageParam): 152 | parts = render_str_or_parts(message["content"]) 153 | message["content"] = check_type(parts, List[ChatCompletionContentPartParam]) 154 | return message 155 | 156 | def render_system_message(message: ChatCompletionSystemMessageParam): 157 | parts = render_str_or_parts(message["content"]) 158 | message["content"] = check_type(parts, List[ChatCompletionContentPartTextParam]) 159 | return message 160 | 161 | def render_assistant_message(message: ChatCompletionAssistantMessageParam): 162 | content = message.get("content", None) 163 | if content is None: 164 | return message 165 | rendered = render_str_or_parts(content) 166 | message["content"] = check_type(rendered, List[ContentArrayOfContentPart]) 167 | return message 168 | 169 | answer = list(template) 170 | for message in answer: 171 | if message["role"] == "system": 172 | render_system_message(message) 173 | elif message["role"] == "user": 174 | render_user_message(message) 175 | elif message["role"] == "assistant": 176 | render_assistant_message(message) 177 | else: 178 | pass 179 | return answer 180 | -------------------------------------------------------------------------------- /src/prompt_bottle/presets/simple.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from prompt_bottle.bottle import PromptBottle 4 | 5 | 6 | def simple_bottle(system: Optional[str], user: str) -> PromptBottle: 7 | if system: 8 | return PromptBottle( 9 | template=[ 10 | {"role": "system", "content": system}, 11 | {"role": "user", "content": user}, 12 | ] 13 | ) 14 | else: 15 | return PromptBottle(template=[{"role": "user", "content": user}]) 16 | -------------------------------------------------------------------------------- /src/prompt_bottle/tags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeautyyuYanli/Prompt-Bottle/826e705b56c3eb213b1d32a6b10c7118920e561b/src/prompt_bottle/tags/__init__.py -------------------------------------------------------------------------------- /src/prompt_bottle/tags/convert_to.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict 2 | 3 | import yaml 4 | from openai.types.chat import ( 5 | ChatCompletionContentPartImageParam, 6 | ChatCompletionContentPartInputAudioParam, 7 | ChatCompletionContentPartParam, 8 | ChatCompletionContentPartTextParam, 9 | ) 10 | from openai.types.chat.chat_completion_content_part_input_audio_param import InputAudio 11 | 12 | from prompt_bottle.tags.tags import PBTag 13 | from prompt_bottle.utils import check_type 14 | 15 | 16 | def to_image_url_part(url: str) -> ChatCompletionContentPartImageParam: 17 | return { 18 | "image_url": {"url": url}, 19 | "type": "image_url", 20 | } 21 | 22 | 23 | def to_text_part(content: str) -> ChatCompletionContentPartTextParam: 24 | return { 25 | "text": content, 26 | "type": "text", 27 | } 28 | 29 | 30 | def to_audio_part(content: str) -> ChatCompletionContentPartInputAudioParam: 31 | return { 32 | "input_audio": check_type(yaml.safe_load(content), type=InputAudio), 33 | "type": "input_audio", 34 | } 35 | 36 | 37 | def to_wav_audio_part(content: str) -> ChatCompletionContentPartInputAudioParam: 38 | return { 39 | "input_audio": {"data": content, "format": "wav"}, 40 | "type": "input_audio", 41 | } 42 | 43 | 44 | def to_mp3_audio_part(content: str) -> ChatCompletionContentPartInputAudioParam: 45 | return { 46 | "input_audio": {"data": content, "format": "mp3"}, 47 | "type": "input_audio", 48 | } 49 | 50 | 51 | # Mapping from PBTag to conversion function 52 | PB_TAG_TO_OPENAI: Dict[PBTag, Callable[[str], ChatCompletionContentPartParam]] = { 53 | PBTag.IMG_URL: to_image_url_part, 54 | PBTag.TEXT: to_text_part, 55 | PBTag.WAV_AUDIO: to_wav_audio_part, 56 | PBTag.MP3_AUDIO: to_mp3_audio_part, 57 | PBTag.AUDIO: to_audio_part, 58 | } 59 | -------------------------------------------------------------------------------- /src/prompt_bottle/tags/tags.py: -------------------------------------------------------------------------------- 1 | import json 2 | from enum import Enum 3 | from typing import Any, Dict, Union, cast 4 | 5 | import yaml 6 | from openai.types.chat.chat_completion_content_part_input_audio_param import InputAudio 7 | 8 | 9 | class PBTag(str, Enum): 10 | IMG_URL = "PROMPT_BOTTLE_IMG_URL" 11 | TEXT = "PROMPT_BOTTLE_TEXT" 12 | WAV_AUDIO = "PROMPT_BOTTLE_WAV_AUDIO" 13 | MP3_AUDIO = "PROMPT_BOTTLE_MP3_AUDIO" 14 | AUDIO = "PROMPT_BOTTLE_AUDIO" 15 | 16 | 17 | def pb_tag_regex(tag: PBTag): 18 | return rf"<{tag.value}>(.*?)" 19 | 20 | 21 | def pb_tag(tag: PBTag, content: Union[str, Dict[str, Any]]): 22 | if isinstance(content, str): 23 | return f"<{tag.value}>{content}" 24 | else: 25 | return ( 26 | f"<{tag.value}>" 27 | + json.dumps( 28 | yaml.safe_dump( 29 | content, default_flow_style=True, width=float("inf") 30 | ).strip() 31 | )[1:-1] 32 | + f"" 33 | ) 34 | 35 | 36 | def pb_img_url(url: str): 37 | return pb_tag(PBTag.IMG_URL, url) 38 | 39 | 40 | def pb_wav_audio(b64: str): 41 | return pb_tag(PBTag.WAV_AUDIO, b64) 42 | 43 | 44 | def pb_mp3_audio(b64: str): 45 | return pb_tag(PBTag.MP3_AUDIO, b64) 46 | 47 | 48 | def pb_audio(input_audio: InputAudio): 49 | return pb_tag(PBTag.AUDIO, cast(dict, input_audio)) 50 | -------------------------------------------------------------------------------- /src/prompt_bottle/utils.py: -------------------------------------------------------------------------------- 1 | from functools import cache 2 | from typing import Any, Type, TypeVar 3 | 4 | from pydantic import TypeAdapter 5 | 6 | T = TypeVar("T") 7 | 8 | 9 | @cache 10 | def _get_type_adapter(type: Type[T]) -> TypeAdapter[T]: 11 | return TypeAdapter(type) 12 | 13 | 14 | def check_type(obj: Any, type: Type[T]) -> T: 15 | adapter = _get_type_adapter(type) 16 | return adapter.validate_python(obj) # type: ignore 17 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeautyyuYanli/Prompt-Bottle/826e705b56c3eb213b1d32a6b10c7118920e561b/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_bottle.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import yaml 4 | 5 | from prompt_bottle import ( 6 | PromptBottle, 7 | pb_audio, 8 | pb_img_url, 9 | pb_mp3_audio, 10 | pb_wav_audio, 11 | ) 12 | 13 | 14 | class TestBottle(TestCase): 15 | def test_text_tag_render(self): 16 | bottle = PromptBottle( 17 | [ 18 | { 19 | "role": "user", 20 | "content": "{{arg0}}{{arg1}}{{arg2}}{{arg3}}{{arg4}}{{arg5}}{{arg6}}", 21 | }, 22 | ] 23 | ) 24 | rendered = bottle.render( 25 | arg0=pb_audio( 26 | { 27 | "data": "Hello", 28 | "format": "wav", 29 | } 30 | ), 31 | arg1="Hello", 32 | arg2=pb_img_url("https://example.com/image.png"), 33 | arg3="!", 34 | arg4=pb_mp3_audio("mp3b64"), 35 | arg5=pb_wav_audio("wavb64"), 36 | arg6="World!", 37 | ) 38 | assert rendered == [ 39 | { 40 | "content": [ 41 | { 42 | "input_audio": {"data": "Hello", "format": "wav"}, 43 | "type": "input_audio", 44 | }, 45 | {"text": "Hello", "type": "text"}, 46 | { 47 | "image_url": {"url": "https://example.com/image.png"}, 48 | "type": "image_url", 49 | }, 50 | {"text": "!", "type": "text"}, 51 | { 52 | "input_audio": {"data": "mp3b64", "format": "mp3"}, 53 | "type": "input_audio", 54 | }, 55 | { 56 | "input_audio": {"data": "wavb64", "format": "wav"}, 57 | "type": "input_audio", 58 | }, 59 | {"text": "World!", "type": "text"}, 60 | ], 61 | "role": "user", 62 | } 63 | ] 64 | return rendered 65 | 66 | def test_audio_by_raw_string(self): 67 | bottle = PromptBottle([{"role": "user", "content": "{{arg0}}"}]) 68 | rendered = bottle.render( 69 | arg0="{data: anything, format: mp3}" 70 | ) 71 | assert rendered == [ 72 | { 73 | "content": [ 74 | { 75 | "input_audio": {"data": "anything", "format": "mp3"}, 76 | "type": "input_audio", 77 | }, 78 | ], 79 | "role": "user", 80 | } 81 | ] 82 | return rendered 83 | 84 | def test_complicated_template_arg(self): 85 | bottle = PromptBottle([{"role": "user", "content": "{{arg0}}"}]) 86 | arg0 = { 87 | "hello": [1, 2, 3], 88 | "world": ["'", '"'], 89 | } 90 | rendered = bottle.render(arg0=arg0) 91 | assert yaml.safe_load(rendered[0]["content"][0]["text"]) == arg0 # type: ignore 92 | 93 | 94 | if __name__ == "__main__": 95 | from rich import print 96 | 97 | # print(TestBottle().test_text_tag_render()) 98 | # print(TestBottle().test_audio_by_raw_string()) 99 | print(TestBottle().test_complicated_template_arg()) 100 | --------------------------------------------------------------------------------