├── .devcontainer
└── devcontainer.json
├── .editorconfig
├── .github
└── workflows
│ ├── integration-tests.yml
│ ├── pre-commit.yaml
│ └── release.yml
├── .gitignore
├── .pre-commit-config.yaml
├── LICENSE
├── Makefile
├── README.md
├── __init__.py
├── conftest.py
├── poetry.lock
├── promptlayer
├── __init__.py
├── groups
│ ├── __init__.py
│ └── groups.py
├── promptlayer.py
├── promptlayer_base.py
├── promptlayer_mixins.py
├── span_exporter.py
├── templates.py
├── track
│ ├── __init__.py
│ └── track.py
├── types
│ ├── __init__.py
│ ├── prompt_template.py
│ └── request_log.py
└── utils.py
├── pyproject.toml
└── tests
├── fixtures
├── __init__.py
├── auth.py
├── cassettes
│ ├── test_anthropic_chat_completion.yaml
│ ├── test_anthropic_chat_completion_async.yaml
│ ├── test_anthropic_chat_completion_async_stream_with_pl_id.yaml
│ ├── test_anthropic_chat_completion_with_pl_id.yaml
│ ├── test_anthropic_chat_completion_with_stream.yaml
│ ├── test_anthropic_chat_completion_with_stream_and_pl_id.yaml
│ ├── test_arun_workflow_request.yaml
│ ├── test_get_all_templates.yaml
│ ├── test_get_final_output_1.yaml
│ ├── test_get_final_output_2.yaml
│ ├── test_get_prompt_template_provider_base_url_name.yaml
│ ├── test_get_template_async.yaml
│ ├── test_log_request_async.yaml
│ ├── test_make_message_listener_1.yaml
│ ├── test_make_message_listener_2.yaml
│ ├── test_openai_chat_completion.yaml
│ ├── test_openai_chat_completion_async.yaml
│ ├── test_openai_chat_completion_async_stream_with_pl_id.yaml
│ ├── test_openai_chat_completion_with_pl_id.yaml
│ ├── test_openai_chat_completion_with_stream.yaml
│ ├── test_openai_chat_completion_with_stream_and_pl_id.yaml
│ ├── test_publish_template_async.yaml
│ ├── test_run_prompt_async.yaml
│ └── test_track_and_templates.yaml
├── clients.py
├── setup.py
├── templates.py
└── workflow_update_messages.py
├── test_agents
├── __init__.py
├── test_arun_workflow_request.py
└── test_misc.py
├── test_anthropic_proxy.py
├── test_get_prompt_template.py
├── test_openai_proxy.py
├── test_promptlayer_run.py
├── test_templates_groups_track.py
└── utils
├── mocks.py
└── vcr.py
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2 | // README at: https://github.com/devcontainers/templates/tree/main/src/python
3 | {
4 | "name": "Python 3",
5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6 | "image": "mcr.microsoft.com/devcontainers/python:1-3.9-bullseye",
7 | "features": {
8 | "ghcr.io/devcontainers-contrib/features/poetry:2": {}
9 | },
10 | "customizations": {
11 | "vscode": {
12 | "extensions": [
13 | "ms-python.python",
14 | "charliermarsh.ruff"
15 | ],
16 | "settings": {
17 | "editor.formatOnSave": true,
18 | "python.analysis.autoImportCompletions": true,
19 | "[python]": {
20 | "editor.defaultFormatter": "charliermarsh.ruff"
21 | },
22 | // TODO(dmu) HIGH: Make linter configuration consistent with .pre-commit-config.yaml
23 | "ruff.organizeImports": true
24 | }
25 | }
26 | },
27 |
28 | // Features to add to the dev container. More info: https://containers.dev/features.
29 | // "features": {},
30 |
31 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
32 | // "forwardPorts": [],
33 |
34 | // Use 'postCreateCommand' to run commands after the container is created.
35 | "postCreateCommand": "poetry install"
36 |
37 | // Configure tool-specific properties.
38 | // "customizations": {},
39 |
40 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
41 | // "remoteUser": "root",
42 | }
43 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # See https://editorconfig.org/ for more info about this file
2 | [*.py]
3 | max_line_length = 120
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 4
7 |
--------------------------------------------------------------------------------
/.github/workflows/integration-tests.yml:
--------------------------------------------------------------------------------
1 | name: Integration Tests
2 |
3 | on:
4 | push:
5 | branches: [$default-branch]
6 |
7 | workflow_dispatch:
8 |
9 | env:
10 | PROMPTLAYER_API_KEY: ${{ secrets.PROMPTLAYER_API_KEY }}
11 | OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
12 |
13 | jobs:
14 | integration-tests:
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Set up Python
20 | uses: actions/setup-python@v3
21 | with:
22 | python-version: "3.x"
23 | - name: Install dependencies
24 | run: |
25 | python -m pip install --upgrade pip
26 | python -m pip install behave openai
27 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
28 | - name: Run Integration Tests
29 | run: behave
30 |
--------------------------------------------------------------------------------
/.github/workflows/pre-commit.yaml:
--------------------------------------------------------------------------------
1 | name: pre-commit
2 |
3 | on: [push]
4 |
5 | jobs:
6 | pre-commit:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - uses: actions/checkout@v3
11 | - name: Setup Python
12 | uses: actions/setup-python@v4
13 | with:
14 | python-version: "3.x"
15 | cache: "pip"
16 |
17 | - name: Install pre-commit
18 | run: pip install pre-commit
19 |
20 | - name: Cache pre-commit
21 | uses: actions/cache@v3
22 | with:
23 | path: ~/.cache/pre-commit
24 | key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
25 |
26 | - name: Run pre-commit hooks
27 | run: pre-commit run -a
28 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Upload Python Package
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Check out code
13 | uses: actions/checkout@v4
14 |
15 | - name: Set up Python
16 | uses: actions/setup-python@v5
17 | with:
18 | python-version: "3.9"
19 |
20 | - name: Install poetry
21 | run: pipx install poetry
22 |
23 | - name: Running poetry install
24 | run: poetry install
25 |
26 | - name: Build and publish
27 | env:
28 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_PASSWORD }}
29 | run: poetry publish --build
30 |
--------------------------------------------------------------------------------
/.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 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
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 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pycharm
129 | .idea
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
134 | .test/
135 | plvenv
136 | test.py
137 |
138 | # VSCode
139 | .vscode/
140 |
141 | # Internal development
142 | /samples
143 | testing_*
144 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v5.0.0
4 | hooks:
5 | - id: end-of-file-fixer
6 | exclude: (app/utils/evaluate/(javascript_code_wrapper|python_code_wrapper)\.txt|langchain_prompts\.txt)
7 | - id: trailing-whitespace
8 | - repo: https://github.com/astral-sh/ruff-pre-commit
9 | rev: v0.9.4
10 | hooks:
11 | - id: ruff
12 | name: 'ruff: fix imports'
13 | args: ["--select", "I", "--fix"]
14 | - id: ruff
15 | - id: ruff-format
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | RUN_TEST := test -f .env && set -a; . ./.env; set +a; poetry run pytest
2 |
3 | .PHONY: lint
4 | lint:
5 | poetry run pre-commit run --all-files
6 |
7 | .PHONY: test
8 | test:
9 | ${RUN_TEST}
10 |
11 | .PHONY: test-sw
12 | test-sw:
13 | ${RUN_TEST} -vv --sw --show-capture=no
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # 🍰 PromptLayer
4 |
5 | **The first platform built for
prompt engineers **
6 |
7 |
8 |
9 |
10 |
11 | ---
12 |
13 |
14 |
15 | [PromptLayer](https://promptlayer.com/) is the first platform that allows you to track, manage, and share your GPT prompt engineering. PromptLayer acts a middleware between your code and OpenAI’s python library.
16 |
17 | PromptLayer records all your OpenAI API requests, allowing you to search and explore request history in the PromptLayer dashboard.
18 |
19 | This repo contains the Python wrapper library for PromptLayer.
20 |
21 | ## Quickstart ⚡
22 |
23 | ### Install PromptLayer
24 |
25 | ```bash
26 | pip install promptlayer
27 | ```
28 |
29 | ### Installing PromptLayer Locally
30 |
31 | Use `pip install .` to install locally.
32 |
33 | ### Using PromptLayer
34 |
35 | To get started, create an account by clicking “*Log in*” on [PromptLayer](https://promptlayer.com/). Once logged in, click the button to create an API key and save this in a secure location ([Guide to Using Env Vars](https://towardsdatascience.com/the-quick-guide-to-using-environment-variables-in-python-d4ec9291619e)).
36 |
37 | Once you have that all set up, [install PromptLayer using](https://pypi.org/project/promptlayer/) `pip`.
38 |
39 | In the Python file where you use OpenAI APIs, add the following. This allows us to keep track of your requests without needing any other code changes.
40 |
41 | ```python
42 | from promptlayer import PromptLayer
43 |
44 | promptlayer = PromptLayer(api_key="
")
45 | openai = promptlayer.openai
46 | ```
47 |
48 | **You can then use `openai` as you would if you had imported it directly.**
49 |
50 |
51 | 💡 Your OpenAI API Key is **never** sent to our servers. All OpenAI requests are made locally from your machine, PromptLayer just logs the request.
52 |
53 |
54 | ### Adding PromptLayer tags: `pl_tags`
55 |
56 | PromptLayer allows you to add tags through the `pl_tags` argument. This allows you to track and group requests in the dashboard.
57 |
58 | *Tags are not required but we recommend them!*
59 |
60 | ```python
61 | openai.Completion.create(
62 | engine="text-ada-001",
63 | prompt="My name is",
64 | pl_tags=["name-guessing", "pipeline-2"]
65 | )
66 | ```
67 |
68 | After making your first few requests, you should be able to see them in the PromptLayer dashboard!
69 |
70 | ## Using the REST API
71 |
72 | This Python library is a wrapper over PromptLayer's REST API. If you use another language, like Javascript, just interact directly with the API.
73 |
74 | Here is an example request below:
75 |
76 | ```python
77 | import requests
78 | request_response = requests.post(
79 | "https://api.promptlayer.com/track-request",
80 | json={
81 | "function_name": "openai.Completion.create",
82 | "args": [],
83 | "kwargs": {"engine": "text-ada-001", "prompt": "My name is"},
84 | "tags": ["hello", "world"],
85 | "request_response": {"id": "cmpl-6TEeJCRVlqQSQqhD8CYKd1HdCcFxM", "object": "text_completion", "created": 1672425843, "model": "text-ada-001", "choices": [{"text": " advocacy\"\n\nMy name is advocacy.", "index": 0, "logprobs": None, "finish_reason": "stop"}]},
86 | "request_start_time": 1673987077.463504,
87 | "request_end_time": 1673987077.463504,
88 | "api_key": "pl_",
89 | },
90 | )
91 | ```
92 |
93 | ## Contributing
94 |
95 | We welcome contributions to our open source project, including new features, infrastructure improvements, and better documentation. For more information or any questions, contact us at [hello@promptlayer.com](mailto:hello@promptlayer.com).
96 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | # TODO(dmu) LOW: This file seems unnecessary. Consider removal
2 |
--------------------------------------------------------------------------------
/conftest.py:
--------------------------------------------------------------------------------
1 | # This file need to be in the root of repo, so it is imported before tests
2 | import pytest
3 |
4 | # we need this to get assert diffs everywhere `tests.*`, it must execute before importing `tests`
5 | pytest.register_assert_rewrite("tests")
6 |
7 | from tests.fixtures import * # noqa: F401, F403, E402
8 |
--------------------------------------------------------------------------------
/promptlayer/__init__.py:
--------------------------------------------------------------------------------
1 | from .promptlayer import AsyncPromptLayer, PromptLayer
2 |
3 | __version__ = "1.0.50"
4 | __all__ = ["PromptLayer", "AsyncPromptLayer", "__version__"]
5 |
--------------------------------------------------------------------------------
/promptlayer/groups/__init__.py:
--------------------------------------------------------------------------------
1 | from promptlayer.groups.groups import acreate, create
2 |
3 |
4 | class GroupManager:
5 | def __init__(self, api_key: str):
6 | self.api_key = api_key
7 |
8 | def create(self):
9 | return create(self.api_key)
10 |
11 |
12 | class AsyncGroupManager:
13 | def __init__(self, api_key: str):
14 | self.api_key = api_key
15 |
16 | async def create(self) -> str:
17 | return await acreate(self.api_key)
18 |
19 |
20 | __all__ = ["GroupManager", "AsyncGroupManager"]
21 |
--------------------------------------------------------------------------------
/promptlayer/groups/groups.py:
--------------------------------------------------------------------------------
1 | from promptlayer.utils import apromptlayer_create_group, promptlayer_create_group
2 |
3 |
4 | def create(api_key: str = None):
5 | """Create a new group."""
6 | return promptlayer_create_group(api_key)
7 |
8 |
9 | async def acreate(api_key: str = None) -> str:
10 | """Asynchronously create a new group."""
11 | return await apromptlayer_create_group(api_key)
12 |
--------------------------------------------------------------------------------
/promptlayer/promptlayer_base.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import inspect
3 | import re
4 |
5 | from promptlayer.utils import async_wrapper, promptlayer_api_handler
6 |
7 |
8 | class PromptLayerBase(object):
9 | __slots__ = [
10 | "_obj",
11 | "__weakref__",
12 | "_function_name",
13 | "_provider_type",
14 | "_api_key",
15 | "_tracer",
16 | ]
17 |
18 | def __init__(self, obj, function_name="", provider_type="openai", api_key=None, tracer=None):
19 | object.__setattr__(self, "_obj", obj)
20 | object.__setattr__(self, "_function_name", function_name)
21 | object.__setattr__(self, "_provider_type", provider_type)
22 | object.__setattr__(self, "_api_key", api_key)
23 | object.__setattr__(self, "_tracer", tracer)
24 |
25 | def __getattr__(self, name):
26 | attr = getattr(object.__getattribute__(self, "_obj"), name)
27 |
28 | if (
29 | name != "count_tokens" # fix for anthropic count_tokens
30 | and not re.match(r"", str(attr)) # fix for anthropic errors
31 | and not re.match(r"", str(attr)) # fix for openai errors
32 | and (
33 | inspect.isclass(attr)
34 | or inspect.isfunction(attr)
35 | or inspect.ismethod(attr)
36 | or str(type(attr)) == ""
37 | or str(type(attr)) == ""
38 | or str(type(attr)) == ""
39 | or str(type(attr)) == ""
40 | or re.match(r"", str(type(attr)))
41 | )
42 | ):
43 | return PromptLayerBase(
44 | attr,
45 | function_name=f"{object.__getattribute__(self, '_function_name')}.{name}",
46 | provider_type=object.__getattribute__(self, "_provider_type"),
47 | api_key=object.__getattribute__(self, "_api_key"),
48 | tracer=object.__getattribute__(self, "_tracer"),
49 | )
50 | return attr
51 |
52 | def __delattr__(self, name):
53 | delattr(object.__getattribute__(self, "_obj"), name)
54 |
55 | def __setattr__(self, name, value):
56 | setattr(object.__getattribute__(self, "_obj"), name, value)
57 |
58 | def __call__(self, *args, **kwargs):
59 | tags = kwargs.pop("pl_tags", None)
60 | if tags is not None and not isinstance(tags, list):
61 | raise Exception("pl_tags must be a list of strings.")
62 |
63 | return_pl_id = kwargs.pop("return_pl_id", False)
64 | request_start_time = datetime.datetime.now().timestamp()
65 | function_object = object.__getattribute__(self, "_obj")
66 | tracer = object.__getattribute__(self, "_tracer")
67 | function_name = object.__getattribute__(self, "_function_name")
68 |
69 | if tracer:
70 | with tracer.start_as_current_span(function_name) as llm_request_span:
71 | llm_request_span_id = hex(llm_request_span.context.span_id)[2:].zfill(16)
72 | llm_request_span.set_attribute("provider", object.__getattribute__(self, "_provider_type"))
73 | llm_request_span.set_attribute("function_name", function_name)
74 | llm_request_span.set_attribute("function_input", str({"args": args, "kwargs": kwargs}))
75 |
76 | if inspect.isclass(function_object):
77 | result = PromptLayerBase(
78 | function_object(*args, **kwargs),
79 | function_name=function_name,
80 | provider_type=object.__getattribute__(self, "_provider_type"),
81 | api_key=object.__getattribute__(self, "_api_key"),
82 | tracer=tracer,
83 | )
84 | llm_request_span.set_attribute("function_output", str(result))
85 | return result
86 |
87 | function_response = function_object(*args, **kwargs)
88 |
89 | if inspect.iscoroutinefunction(function_object) or inspect.iscoroutine(function_response):
90 | return async_wrapper(
91 | function_response,
92 | return_pl_id,
93 | request_start_time,
94 | function_name,
95 | object.__getattribute__(self, "_provider_type"),
96 | tags,
97 | api_key=object.__getattribute__(self, "_api_key"),
98 | llm_request_span_id=llm_request_span_id,
99 | tracer=tracer, # Pass the tracer to async_wrapper
100 | *args,
101 | **kwargs,
102 | )
103 |
104 | request_end_time = datetime.datetime.now().timestamp()
105 | result = promptlayer_api_handler(
106 | function_name,
107 | object.__getattribute__(self, "_provider_type"),
108 | args,
109 | kwargs,
110 | tags,
111 | function_response,
112 | request_start_time,
113 | request_end_time,
114 | object.__getattribute__(self, "_api_key"),
115 | return_pl_id=return_pl_id,
116 | llm_request_span_id=llm_request_span_id,
117 | )
118 | llm_request_span.set_attribute("function_output", str(result))
119 | return result
120 | else:
121 | # Without tracing
122 | if inspect.isclass(function_object):
123 | return PromptLayerBase(
124 | function_object(*args, **kwargs),
125 | function_name=function_name,
126 | provider_type=object.__getattribute__(self, "_provider_type"),
127 | api_key=object.__getattribute__(self, "_api_key"),
128 | )
129 |
130 | function_response = function_object(*args, **kwargs)
131 |
132 | if inspect.iscoroutinefunction(function_object) or inspect.iscoroutine(function_response):
133 | return async_wrapper(
134 | function_response,
135 | return_pl_id,
136 | request_start_time,
137 | function_name,
138 | object.__getattribute__(self, "_provider_type"),
139 | tags,
140 | api_key=object.__getattribute__(self, "_api_key"),
141 | *args,
142 | **kwargs,
143 | )
144 |
145 | request_end_time = datetime.datetime.now().timestamp()
146 | return promptlayer_api_handler(
147 | function_name,
148 | object.__getattribute__(self, "_provider_type"),
149 | args,
150 | kwargs,
151 | tags,
152 | function_response,
153 | request_start_time,
154 | request_end_time,
155 | object.__getattribute__(self, "_api_key"),
156 | return_pl_id=return_pl_id,
157 | )
158 |
--------------------------------------------------------------------------------
/promptlayer/promptlayer_mixins.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import datetime
3 | from copy import deepcopy
4 | from functools import wraps
5 | from typing import Dict, Union
6 |
7 | from opentelemetry.sdk.resources import Resource
8 | from opentelemetry.sdk.trace import TracerProvider
9 | from opentelemetry.sdk.trace.export import BatchSpanProcessor
10 | from opentelemetry.semconv.resource import ResourceAttributes
11 |
12 | from promptlayer.span_exporter import PromptLayerSpanExporter
13 | from promptlayer.utils import (
14 | aanthropic_request,
15 | aanthropic_stream_completion,
16 | aanthropic_stream_message,
17 | aazure_openai_request,
18 | agoogle_request,
19 | agoogle_stream_chat,
20 | agoogle_stream_completion,
21 | amistral_request,
22 | amistral_stream_chat,
23 | anthropic_request,
24 | anthropic_stream_completion,
25 | anthropic_stream_message,
26 | aopenai_request,
27 | aopenai_stream_chat,
28 | aopenai_stream_completion,
29 | azure_openai_request,
30 | google_request,
31 | google_stream_chat,
32 | google_stream_completion,
33 | mistral_request,
34 | mistral_stream_chat,
35 | openai_request,
36 | openai_stream_chat,
37 | openai_stream_completion,
38 | )
39 |
40 | MAP_PROVIDER_TO_FUNCTION_NAME = {
41 | "openai": {
42 | "chat": {
43 | "function_name": "openai.chat.completions.create",
44 | "stream_function": openai_stream_chat,
45 | },
46 | "completion": {
47 | "function_name": "openai.completions.create",
48 | "stream_function": openai_stream_completion,
49 | },
50 | },
51 | "anthropic": {
52 | "chat": {
53 | "function_name": "anthropic.messages.create",
54 | "stream_function": anthropic_stream_message,
55 | },
56 | "completion": {
57 | "function_name": "anthropic.completions.create",
58 | "stream_function": anthropic_stream_completion,
59 | },
60 | },
61 | "openai.azure": {
62 | "chat": {
63 | "function_name": "openai.AzureOpenAI.chat.completions.create",
64 | "stream_function": openai_stream_chat,
65 | },
66 | "completion": {
67 | "function_name": "openai.AzureOpenAI.completions.create",
68 | "stream_function": openai_stream_completion,
69 | },
70 | },
71 | "mistral": {
72 | "chat": {
73 | "function_name": "mistral.client.chat",
74 | "stream_function": mistral_stream_chat,
75 | },
76 | "completion": {
77 | "function_name": None,
78 | "stream_function": None,
79 | },
80 | },
81 | "google": {
82 | "chat": {
83 | "function_name": "google.convo.send_message",
84 | "stream_function": google_stream_chat,
85 | },
86 | "completion": {
87 | "function_name": "google.model.generate_content",
88 | "stream_function": google_stream_completion,
89 | },
90 | },
91 | }
92 |
93 |
94 | MAP_PROVIDER_TO_FUNCTION = {
95 | "openai": openai_request,
96 | "anthropic": anthropic_request,
97 | "openai.azure": azure_openai_request,
98 | "mistral": mistral_request,
99 | "google": google_request,
100 | }
101 |
102 | AMAP_PROVIDER_TO_FUNCTION_NAME = {
103 | "openai": {
104 | "chat": {
105 | "function_name": "openai.chat.completions.create",
106 | "stream_function": aopenai_stream_chat,
107 | },
108 | "completion": {
109 | "function_name": "openai.completions.create",
110 | "stream_function": aopenai_stream_completion,
111 | },
112 | },
113 | "anthropic": {
114 | "chat": {
115 | "function_name": "anthropic.messages.create",
116 | "stream_function": aanthropic_stream_message,
117 | },
118 | "completion": {
119 | "function_name": "anthropic.completions.create",
120 | "stream_function": aanthropic_stream_completion,
121 | },
122 | },
123 | "openai.azure": {
124 | "chat": {
125 | "function_name": "openai.AzureOpenAI.chat.completions.create",
126 | "stream_function": aopenai_stream_chat,
127 | },
128 | "completion": {
129 | "function_name": "openai.AzureOpenAI.completions.create",
130 | "stream_function": aopenai_stream_completion,
131 | },
132 | },
133 | "mistral": {
134 | "chat": {
135 | "function_name": "mistral.client.chat",
136 | "stream_function": amistral_stream_chat,
137 | },
138 | "completion": {
139 | "function_name": None,
140 | "stream_function": None,
141 | },
142 | },
143 | "google": {
144 | "chat": {
145 | "function_name": "google.convo.send_message",
146 | "stream_function": agoogle_stream_chat,
147 | },
148 | "completion": {
149 | "function_name": "google.model.generate_content",
150 | "stream_function": agoogle_stream_completion,
151 | },
152 | },
153 | }
154 |
155 |
156 | AMAP_PROVIDER_TO_FUNCTION = {
157 | "openai": aopenai_request,
158 | "anthropic": aanthropic_request,
159 | "openai.azure": aazure_openai_request,
160 | "mistral": amistral_request,
161 | "google": agoogle_request,
162 | }
163 |
164 |
165 | class PromptLayerMixin:
166 | @staticmethod
167 | def _initialize_tracer(api_key: str = None, enable_tracing: bool = False):
168 | if enable_tracing:
169 | resource = Resource(attributes={ResourceAttributes.SERVICE_NAME: "prompt-layer-library"})
170 | tracer_provider = TracerProvider(resource=resource)
171 | promptlayer_exporter = PromptLayerSpanExporter(api_key=api_key)
172 | span_processor = BatchSpanProcessor(promptlayer_exporter)
173 | tracer_provider.add_span_processor(span_processor)
174 | tracer = tracer_provider.get_tracer(__name__)
175 | return tracer_provider, tracer
176 | else:
177 | return None, None
178 |
179 | @staticmethod
180 | def _prepare_get_prompt_template_params(*, prompt_version, prompt_release_label, input_variables, metadata):
181 | params = {}
182 |
183 | if prompt_version:
184 | params["version"] = prompt_version
185 | if prompt_release_label:
186 | params["label"] = prompt_release_label
187 | if input_variables:
188 | params["input_variables"] = input_variables
189 | if metadata:
190 | params["metadata_filters"] = metadata
191 |
192 | return params
193 |
194 | @staticmethod
195 | def _prepare_llm_request_params(
196 | *,
197 | prompt_blueprint,
198 | prompt_template,
199 | prompt_blueprint_model,
200 | model_parameter_overrides,
201 | stream,
202 | is_async=False,
203 | ):
204 | provider = prompt_blueprint_model["provider"]
205 | kwargs = deepcopy(prompt_blueprint["llm_kwargs"])
206 | if is_async:
207 | config = AMAP_PROVIDER_TO_FUNCTION_NAME[provider][prompt_template["type"]]
208 | request_function = AMAP_PROVIDER_TO_FUNCTION[provider]
209 | else:
210 | config = MAP_PROVIDER_TO_FUNCTION_NAME[provider][prompt_template["type"]]
211 | request_function = MAP_PROVIDER_TO_FUNCTION[provider]
212 |
213 | if provider_base_url := prompt_blueprint.get("provider_base_url"):
214 | kwargs["base_url"] = provider_base_url["url"]
215 |
216 | if model_parameter_overrides:
217 | kwargs.update(model_parameter_overrides)
218 |
219 | kwargs["stream"] = stream
220 | if stream and provider in ["openai", "openai.azure"]:
221 | kwargs["stream_options"] = {"include_usage": True}
222 |
223 | return {
224 | "provider": provider,
225 | "function_name": config["function_name"],
226 | "stream_function": config["stream_function"],
227 | "request_function": request_function,
228 | "kwargs": kwargs,
229 | "prompt_blueprint": prompt_blueprint,
230 | }
231 |
232 | @staticmethod
233 | def _validate_and_extract_model_from_prompt_blueprint(*, prompt_blueprint, prompt_name):
234 | if not prompt_blueprint["llm_kwargs"]:
235 | raise ValueError(
236 | f"Prompt '{prompt_name}' does not have any LLM kwargs associated with it. Please set your model parameters in the registry in the PromptLayer dashbaord."
237 | )
238 |
239 | prompt_blueprint_metadata = prompt_blueprint.get("metadata")
240 |
241 | if not prompt_blueprint_metadata:
242 | raise ValueError(f"Prompt '{prompt_name}' does not have any metadata associated with it.")
243 |
244 | prompt_blueprint_model = prompt_blueprint_metadata.get("model")
245 |
246 | if not prompt_blueprint_model:
247 | raise ValueError(f"Prompt '{prompt_name}' does not have a model parameters associated with it.")
248 |
249 | return prompt_blueprint_model
250 |
251 | @staticmethod
252 | def _prepare_track_request_kwargs(
253 | api_key,
254 | request_params,
255 | tags,
256 | input_variables,
257 | group_id,
258 | pl_run_span_id: Union[str, None] = None,
259 | metadata: Union[Dict[str, str], None] = None,
260 | **body,
261 | ):
262 | return {
263 | "function_name": request_params["function_name"],
264 | "provider_type": request_params["provider"],
265 | "args": [],
266 | "kwargs": request_params["kwargs"],
267 | "tags": tags,
268 | "request_start_time": datetime.datetime.now(datetime.timezone.utc).timestamp(),
269 | "request_end_time": datetime.datetime.now(datetime.timezone.utc).timestamp(),
270 | "api_key": api_key,
271 | "metadata": metadata,
272 | "prompt_id": request_params["prompt_blueprint"]["id"],
273 | "prompt_version": request_params["prompt_blueprint"]["version"],
274 | "prompt_input_variables": input_variables or {},
275 | "group_id": group_id,
276 | "return_prompt_blueprint": True,
277 | "span_id": pl_run_span_id,
278 | **body,
279 | }
280 |
281 | def traceable(self, attributes=None, name=None):
282 | def decorator(func):
283 | @wraps(func)
284 | def sync_wrapper(*args, **kwargs):
285 | if self.tracer:
286 | span_name = name or func.__name__
287 | with self.tracer.start_as_current_span(span_name) as span:
288 | if attributes:
289 | for key, value in attributes.items():
290 | span.set_attribute(key, value)
291 |
292 | span.set_attribute("function_input", str({"args": args, "kwargs": kwargs}))
293 | result = func(*args, **kwargs)
294 | span.set_attribute("function_output", str(result))
295 |
296 | return result
297 | else:
298 | return func(*args, **kwargs)
299 |
300 | @wraps(func)
301 | async def async_wrapper(*args, **kwargs):
302 | if self.tracer:
303 | span_name = name or func.__name__
304 | with self.tracer.start_as_current_span(span_name) as span:
305 | if attributes:
306 | for key, value in attributes.items():
307 | span.set_attribute(key, value)
308 |
309 | span.set_attribute("function_input", str({"args": args, "kwargs": kwargs}))
310 | result = await func(*args, **kwargs)
311 | span.set_attribute("function_output", str(result))
312 |
313 | return result
314 | else:
315 | return await func(*args, **kwargs)
316 |
317 | return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper
318 |
319 | return decorator
320 |
--------------------------------------------------------------------------------
/promptlayer/span_exporter.py:
--------------------------------------------------------------------------------
1 | from typing import Sequence
2 |
3 | import requests
4 | from opentelemetry.sdk.trace import ReadableSpan
5 | from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
6 |
7 | from promptlayer.utils import URL_API_PROMPTLAYER
8 |
9 |
10 | class PromptLayerSpanExporter(SpanExporter):
11 | def __init__(self, api_key: str = None):
12 | self.api_key = api_key
13 | self.url = f"{URL_API_PROMPTLAYER}/spans-bulk"
14 |
15 | def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
16 | request_data = []
17 |
18 | for span in spans:
19 | span_info = {
20 | "name": span.name,
21 | "context": {
22 | "trace_id": hex(span.context.trace_id)[2:].zfill(32), # Ensure 32 characters
23 | "span_id": hex(span.context.span_id)[2:].zfill(16), # Ensure 16 characters
24 | "trace_state": str(span.context.trace_state),
25 | },
26 | "kind": str(span.kind),
27 | "parent_id": hex(span.parent.span_id)[2:] if span.parent else None,
28 | "start_time": span.start_time,
29 | "end_time": span.end_time,
30 | "status": {
31 | "status_code": str(span.status.status_code),
32 | "description": span.status.description,
33 | },
34 | "attributes": dict(span.attributes),
35 | "events": [
36 | {
37 | "name": event.name,
38 | "timestamp": event.timestamp,
39 | "attributes": dict(event.attributes),
40 | }
41 | for event in span.events
42 | ],
43 | "links": [{"context": link.context, "attributes": dict(link.attributes)} for link in span.links],
44 | "resource": {
45 | "attributes": dict(span.resource.attributes),
46 | "schema_url": span.resource.schema_url,
47 | },
48 | }
49 | request_data.append(span_info)
50 |
51 | try:
52 | response = requests.post(
53 | self.url,
54 | headers={"X-Api-Key": self.api_key, "Content-Type": "application/json"},
55 | json={"spans": request_data},
56 | )
57 | response.raise_for_status()
58 | return SpanExportResult.SUCCESS
59 | except requests.RequestException:
60 | return SpanExportResult.FAILURE
61 |
62 | def shutdown(self):
63 | pass
64 |
--------------------------------------------------------------------------------
/promptlayer/templates.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | from promptlayer.types.prompt_template import GetPromptTemplate, PublishPromptTemplate
4 | from promptlayer.utils import (
5 | aget_all_prompt_templates,
6 | aget_prompt_template,
7 | get_all_prompt_templates,
8 | get_prompt_template,
9 | publish_prompt_template,
10 | )
11 |
12 |
13 | class TemplateManager:
14 | def __init__(self, api_key: str):
15 | self.api_key = api_key
16 |
17 | def get(self, prompt_name: str, params: Union[GetPromptTemplate, None] = None):
18 | return get_prompt_template(prompt_name, params, self.api_key)
19 |
20 | def publish(self, body: PublishPromptTemplate):
21 | return publish_prompt_template(body, self.api_key)
22 |
23 | def all(self, page: int = 1, per_page: int = 30):
24 | return get_all_prompt_templates(page, per_page, self.api_key)
25 |
26 |
27 | class AsyncTemplateManager:
28 | def __init__(self, api_key: str):
29 | self.api_key = api_key
30 |
31 | async def get(self, prompt_name: str, params: Union[GetPromptTemplate, None] = None):
32 | return await aget_prompt_template(prompt_name, params, self.api_key)
33 |
34 | async def all(self, page: int = 1, per_page: int = 30):
35 | return await aget_all_prompt_templates(page, per_page, self.api_key)
36 |
--------------------------------------------------------------------------------
/promptlayer/track/__init__.py:
--------------------------------------------------------------------------------
1 | from promptlayer.track.track import (
2 | agroup,
3 | ametadata,
4 | aprompt,
5 | ascore,
6 | group,
7 | metadata as metadata_,
8 | prompt,
9 | score as score_,
10 | )
11 |
12 | # TODO(dmu) LOW: Move this code to another file
13 |
14 |
15 | class TrackManager:
16 | def __init__(self, api_key: str):
17 | self.api_key = api_key
18 |
19 | def group(self, request_id, group_id):
20 | return group(request_id, group_id, self.api_key)
21 |
22 | def metadata(self, request_id, metadata):
23 | return metadata_(request_id, metadata, self.api_key)
24 |
25 | def prompt(self, request_id, prompt_name, prompt_input_variables, version=None, label=None):
26 | return prompt(
27 | request_id,
28 | prompt_name,
29 | prompt_input_variables,
30 | version,
31 | label,
32 | self.api_key,
33 | )
34 |
35 | def score(self, request_id, score, score_name=None):
36 | return score_(request_id, score, score_name, self.api_key)
37 |
38 |
39 | class AsyncTrackManager:
40 | def __init__(self, api_key: str):
41 | self.api_key = api_key
42 |
43 | async def group(self, request_id, group_id):
44 | return await agroup(request_id, group_id, self.api_key)
45 |
46 | async def metadata(self, request_id, metadata):
47 | return await ametadata(request_id, metadata, self.api_key)
48 |
49 | async def prompt(self, request_id, prompt_name, prompt_input_variables, version=None, label=None):
50 | return await aprompt(
51 | request_id,
52 | prompt_name,
53 | prompt_input_variables,
54 | version,
55 | label,
56 | self.api_key,
57 | )
58 |
59 | async def score(self, request_id, score, score_name=None):
60 | return await ascore(request_id, score, score_name, self.api_key)
61 |
62 |
63 | __all__ = ["TrackManager"]
64 |
--------------------------------------------------------------------------------
/promptlayer/track/track.py:
--------------------------------------------------------------------------------
1 | from promptlayer.utils import (
2 | apromptlayer_track_group,
3 | apromptlayer_track_metadata,
4 | apromptlayer_track_prompt,
5 | apromptlayer_track_score,
6 | promptlayer_track_group,
7 | promptlayer_track_metadata,
8 | promptlayer_track_prompt,
9 | promptlayer_track_score,
10 | )
11 |
12 |
13 | def prompt(
14 | request_id,
15 | prompt_name,
16 | prompt_input_variables,
17 | version=None,
18 | label=None,
19 | api_key: str = None,
20 | ):
21 | if not isinstance(prompt_input_variables, dict):
22 | raise Exception("Please provide a dictionary of input variables.")
23 | return promptlayer_track_prompt(request_id, prompt_name, prompt_input_variables, api_key, version, label)
24 |
25 |
26 | def metadata(request_id, metadata, api_key: str = None):
27 | if not isinstance(metadata, dict):
28 | raise Exception("Please provide a dictionary of metadata.")
29 | for key, value in metadata.items():
30 | if not isinstance(key, str) or not isinstance(value, str):
31 | raise Exception("Please provide a dictionary of metadata with key value pair of strings.")
32 | return promptlayer_track_metadata(request_id, metadata, api_key)
33 |
34 |
35 | def score(request_id, score, score_name=None, api_key: str = None):
36 | if not isinstance(score, int):
37 | raise Exception("Please provide a int score.")
38 | if not isinstance(score_name, str) and score_name is not None:
39 | raise Exception("Please provide a string as score name.")
40 | if score < 0 or score > 100:
41 | raise Exception("Please provide a score between 0 and 100.")
42 | return promptlayer_track_score(request_id, score, score_name, api_key)
43 |
44 |
45 | def group(request_id, group_id, api_key: str = None):
46 | return promptlayer_track_group(request_id, group_id, api_key)
47 |
48 |
49 | async def aprompt(
50 | request_id,
51 | prompt_name,
52 | prompt_input_variables,
53 | version=None,
54 | label=None,
55 | api_key: str = None,
56 | ):
57 | if not isinstance(prompt_input_variables, dict):
58 | raise Exception("Please provide a dictionary of input variables.")
59 | return await apromptlayer_track_prompt(request_id, prompt_name, prompt_input_variables, api_key, version, label)
60 |
61 |
62 | async def ametadata(request_id, metadata, api_key: str = None):
63 | if not isinstance(metadata, dict):
64 | raise Exception("Please provide a dictionary of metadata.")
65 | for key, value in metadata.items():
66 | if not isinstance(key, str) or not isinstance(value, str):
67 | raise Exception("Please provide a dictionary of metadata with key-value pairs of strings.")
68 | return await apromptlayer_track_metadata(request_id, metadata, api_key)
69 |
70 |
71 | async def ascore(request_id, score, score_name=None, api_key: str = None):
72 | if not isinstance(score, int):
73 | raise Exception("Please provide an integer score.")
74 | if not isinstance(score_name, str) and score_name is not None:
75 | raise Exception("Please provide a string as score name.")
76 | if score < 0 or score > 100:
77 | raise Exception("Please provide a score between 0 and 100.")
78 | return await apromptlayer_track_score(request_id, score, score_name, api_key)
79 |
80 |
81 | async def agroup(request_id, group_id, api_key: str = None):
82 | return await apromptlayer_track_group(request_id, group_id, api_key)
83 |
--------------------------------------------------------------------------------
/promptlayer/types/__init__.py:
--------------------------------------------------------------------------------
1 | from . import prompt_template
2 | from .request_log import RequestLog
3 |
4 | __all__ = ["prompt_template", "RequestLog"]
5 |
--------------------------------------------------------------------------------
/promptlayer/types/prompt_template.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Dict, List, Literal, Optional, Sequence, TypedDict, Union
2 |
3 | from typing_extensions import Required
4 |
5 |
6 | class GetPromptTemplate(TypedDict, total=False):
7 | version: int
8 | label: str
9 | provider: str
10 | input_variables: Dict[str, Any]
11 | metadata_filters: Dict[str, str]
12 |
13 |
14 | TemplateFormat = Literal["f-string", "jinja2"]
15 |
16 |
17 | class ImageUrl(TypedDict, total=False):
18 | url: str
19 |
20 |
21 | class TextContent(TypedDict, total=False):
22 | type: Literal["text"]
23 | text: str
24 |
25 |
26 | class ImageContent(TypedDict, total=False):
27 | type: Literal["image_url"]
28 | image_url: ImageUrl
29 |
30 |
31 | class Media(TypedDict, total=False):
32 | title: str
33 | type: str
34 | url: str
35 |
36 |
37 | class MediaContnt(TypedDict, total=False):
38 | type: Literal["media"]
39 | media: Media
40 |
41 |
42 | class MediaVariable(TypedDict, total=False):
43 | type: Literal["media_variable"]
44 | name: str
45 |
46 |
47 | Content = Union[TextContent, ImageContent, MediaContnt, MediaVariable]
48 |
49 |
50 | class Function(TypedDict, total=False):
51 | name: str
52 | description: str
53 | parameters: dict
54 |
55 |
56 | class Tool(TypedDict, total=False):
57 | type: Literal["function"]
58 | function: Function
59 |
60 |
61 | class FunctionCall(TypedDict, total=False):
62 | name: str
63 | arguments: str
64 |
65 |
66 | class SystemMessage(TypedDict, total=False):
67 | role: Literal["system"]
68 | input_variables: List[str]
69 | template_format: TemplateFormat
70 | content: Sequence[Content]
71 | name: str
72 |
73 |
74 | class UserMessage(TypedDict, total=False):
75 | role: Literal["user"]
76 | input_variables: List[str]
77 | template_format: TemplateFormat
78 | content: Sequence[Content]
79 | name: str
80 |
81 |
82 | class ToolCall(TypedDict, total=False):
83 | id: str
84 | type: Literal["function"]
85 | function: FunctionCall
86 |
87 |
88 | class AssistantMessage(TypedDict, total=False):
89 | role: Literal["assistant"]
90 | input_variables: List[str]
91 | template_format: TemplateFormat
92 | content: Sequence[Content]
93 | function_call: FunctionCall
94 | name: str
95 | tool_calls: List[ToolCall]
96 |
97 |
98 | class FunctionMessage(TypedDict, total=False):
99 | role: Literal["function"]
100 | input_variables: List[str]
101 | template_format: TemplateFormat
102 | content: Sequence[Content]
103 | name: str
104 |
105 |
106 | class ToolMessage(TypedDict, total=False):
107 | role: Literal["tool"]
108 | input_variables: List[str]
109 | template_format: TemplateFormat
110 | content: Sequence[Content]
111 | tool_call_id: str
112 | name: str
113 |
114 |
115 | class PlaceholderMessage(TypedDict, total=False):
116 | role: Literal["placeholder"]
117 | name: str
118 |
119 |
120 | class DeveloperMessage(TypedDict, total=False):
121 | role: Literal["developer"]
122 | input_variables: List[str]
123 | template_format: TemplateFormat
124 | content: Sequence[Content]
125 |
126 |
127 | class ChatFunctionCall(TypedDict, total=False):
128 | name: str
129 |
130 |
131 | class ChatToolChoice(TypedDict, total=False):
132 | type: Literal["function"]
133 | function: ChatFunctionCall
134 |
135 |
136 | ToolChoice = Union[str, ChatToolChoice]
137 |
138 | Message = Union[
139 | SystemMessage,
140 | UserMessage,
141 | AssistantMessage,
142 | FunctionMessage,
143 | ToolMessage,
144 | PlaceholderMessage,
145 | DeveloperMessage,
146 | ]
147 |
148 |
149 | class CompletionPromptTemplate(TypedDict, total=False):
150 | type: Required[Literal["completion"]]
151 | template_format: TemplateFormat
152 | content: Sequence[Content]
153 | input_variables: List[str]
154 |
155 |
156 | class ChatPromptTemplate(TypedDict, total=False):
157 | type: Required[Literal["chat"]]
158 | messages: Required[Sequence[Message]]
159 | functions: Sequence[Function]
160 | function_call: Union[Literal["auto", "none"], ChatFunctionCall]
161 | input_variables: List[str]
162 | tools: Sequence[Tool]
163 | tool_choice: ToolChoice
164 |
165 |
166 | PromptTemplate = Union[CompletionPromptTemplate, ChatPromptTemplate]
167 |
168 |
169 | class Model(TypedDict, total=False):
170 | provider: Required[str]
171 | name: Required[str]
172 | parameters: Required[Dict[str, object]]
173 |
174 |
175 | class Metadata(TypedDict, total=False):
176 | model: Model
177 |
178 |
179 | class BasePromptTemplate(TypedDict, total=False):
180 | prompt_name: str
181 | tags: List[str]
182 |
183 |
184 | class PromptBlueprint(TypedDict, total=False):
185 | prompt_template: PromptTemplate
186 | commit_message: str
187 | metadata: Metadata
188 |
189 |
190 | class PublishPromptTemplate(BasePromptTemplate, PromptBlueprint, total=False):
191 | release_labels: Optional[List[str]] = None
192 |
193 |
194 | class BaseProviderBaseURL(TypedDict):
195 | name: Required[str]
196 | provider: Required[str]
197 | url: Required[str]
198 |
199 |
200 | class ProviderBaseURL(BaseProviderBaseURL):
201 | id: Required[int]
202 |
203 |
204 | class BasePromptTemplateResponse(TypedDict, total=False):
205 | id: Required[int]
206 | prompt_name: Required[str]
207 | tags: List[str]
208 | prompt_template: Required[PromptTemplate]
209 | commit_message: str
210 | metadata: Metadata
211 | provider_base_url: ProviderBaseURL
212 |
213 |
214 | a: BasePromptTemplateResponse = {"provider_base_url": {"url": ""}}
215 |
216 |
217 | class PublishPromptTemplateResponse(BasePromptTemplateResponse):
218 | pass
219 |
220 |
221 | class GetPromptTemplateResponse(BasePromptTemplateResponse):
222 | llm_kwargs: Union[Dict[str, object], None]
223 | version: int
224 |
225 |
226 | class ListPromptTemplateResponse(BasePromptTemplateResponse, total=False):
227 | version: int
228 |
--------------------------------------------------------------------------------
/promptlayer/types/request_log.py:
--------------------------------------------------------------------------------
1 | from typing import TypedDict, Union
2 |
3 | from .prompt_template import PromptBlueprint
4 |
5 |
6 | class RequestLog(TypedDict):
7 | id: int
8 | prompt_version: Union[PromptBlueprint, None]
9 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "promptlayer"
3 | version = "1.0.50"
4 | description = "PromptLayer is a platform for prompt engineering and tracks your LLM requests."
5 | authors = ["Magniv "]
6 | license = "Apache-2.0"
7 | readme = "README.md"
8 |
9 | [tool.poetry.dependencies]
10 | python = ">=3.9,<4.0"
11 | requests = "^2.31.0"
12 | opentelemetry-api = "^1.26.0"
13 | opentelemetry-sdk = "^1.26.0"
14 | ably = "^2.0.11"
15 | aiohttp = "^3.10.10"
16 | httpx = "^0.28.1"
17 | nest-asyncio = "^1.6.0"
18 |
19 | [tool.poetry.group.dev.dependencies]
20 | behave = "^1.2.6"
21 | pytest = "^8.2.0"
22 | pytest-asyncio = "^0.23.6"
23 | openai = "^1.60.1"
24 | google-genai = "^1.5.0"
25 | anthropic = "0.49.0"
26 | # TODO(dmu) MEDIUM: Upgrade to vcrpy >= 7 once it supports urllib3 >= 2.2.2
27 | vcrpy = "<7.0.0"
28 | pytest-network = "^0.0.1"
29 | pytest-parametrize-cases = "^0.1.2"
30 |
31 | [build-system]
32 | requires = ["poetry-core"]
33 | build-backend = "poetry.core.masonry.api"
34 |
35 | [tool.ruff]
36 | line-length = 120
37 | indent-width = 4 # mimic Black
38 | target-version = "py38"
39 |
40 | [tool.ruff.lint]
41 | ignore = ["E501", "E711", "E712"]
42 |
43 | [tool.ruff.lint.isort]
44 | combine-as-imports = true
45 | relative-imports-order = "closest-to-furthest"
46 | known-first-party = ["promptlayer", "tests"]
47 |
48 | [tool.ruff.format]
49 | quote-style = "double" # mimic Black
50 | indent-style = "space" # also mimic Black
51 | skip-magic-trailing-comma = false # also mimic Black
52 | line-ending = "auto" # mimic Black
53 |
--------------------------------------------------------------------------------
/tests/fixtures/__init__.py:
--------------------------------------------------------------------------------
1 | from .auth import anthropic_api_key, headers, openai_api_key, promptlayer_api_key # noqa: F401
2 | from .clients import ( # noqa: F401
3 | anthropic_async_client,
4 | anthropic_client,
5 | openai_async_client,
6 | openai_client,
7 | promptlayer_async_client,
8 | promptlayer_client,
9 | )
10 | from .setup import autouse_disable_network, setup # noqa: F401
11 | from .templates import sample_template_content, sample_template_name # noqa: F401
12 | from .workflow_update_messages import (
13 | workflow_update_data_exceeds_size_limit, # noqa: F401
14 | workflow_update_data_no_result_code, # noqa: F401
15 | workflow_update_data_ok, # noqa: F401
16 | )
17 |
--------------------------------------------------------------------------------
/tests/fixtures/auth.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import pytest
4 |
5 |
6 | @pytest.fixture
7 | def promptlayer_api_key():
8 | return os.environ.get("PROMPTLAYER_API_KEY", "pl_sanitized")
9 |
10 |
11 | @pytest.fixture
12 | def anthropic_api_key():
13 | return os.environ.get("ANTHROPIC_API_KEY", "sk-ant-api03-sanitized")
14 |
15 |
16 | @pytest.fixture
17 | def openai_api_key():
18 | return os.environ.get("OPENAI_API_KEY", "sk-sanitized")
19 |
20 |
21 | @pytest.fixture
22 | def headers(promptlayer_api_key):
23 | return {"X-API-KEY": promptlayer_api_key}
24 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_anthropic_chat_completion.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "What is
4 | the capital of the United States?"}], "model": "claude-3-haiku-20240307"}'
5 | headers:
6 | accept:
7 | - application/json
8 | accept-encoding:
9 | - gzip, deflate
10 | anthropic-version:
11 | - '2023-06-01'
12 | connection:
13 | - keep-alive
14 | content-length:
15 | - '136'
16 | content-type:
17 | - application/json
18 | host:
19 | - api.anthropic.com
20 | user-agent:
21 | - Anthropic/Python 0.49.0
22 | x-api-key:
23 | - sanitized
24 | x-stainless-arch:
25 | - x64
26 | x-stainless-async:
27 | - 'false'
28 | x-stainless-lang:
29 | - python
30 | x-stainless-os:
31 | - Linux
32 | x-stainless-package-version:
33 | - 0.49.0
34 | x-stainless-read-timeout:
35 | - '600'
36 | x-stainless-retry-count:
37 | - '0'
38 | x-stainless-runtime:
39 | - CPython
40 | x-stainless-runtime-version:
41 | - 3.9.21
42 | x-stainless-timeout:
43 | - '600'
44 | method: POST
45 | uri: https://api.anthropic.com/v1/messages
46 | response:
47 | body:
48 | string: '{"id":"msg_01TYd9en4fxUtB11teUYmYqv","type":"message","role":"assistant","model":"claude-3-haiku-20240307","content":[{"type":"text","text":"The
49 | capital of the United States is Washington, D.C."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":16,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":16}}'
50 | headers:
51 | CF-RAY:
52 | - 9302be57b990f136-DME
53 | Connection:
54 | - keep-alive
55 | Content-Type:
56 | - application/json
57 | Date:
58 | - Mon, 14 Apr 2025 11:08:08 GMT
59 | Server:
60 | - cloudflare
61 | Transfer-Encoding:
62 | - chunked
63 | X-Robots-Tag:
64 | - none
65 | anthropic-organization-id:
66 | - ec8f80fa-3527-4d15-8ab4-526e6a1f4899
67 | cf-cache-status:
68 | - DYNAMIC
69 | content-length:
70 | - '350'
71 | request-id:
72 | - req_014E9E6P3Z2CNv265MwqWKmQ
73 | via:
74 | - 1.1 google
75 | status:
76 | code: 200
77 | message: OK
78 | - request:
79 | body: '{"function_name": "anthropic.Anthropic.messages.create", "provider_type":
80 | "anthropic", "args": [], "kwargs": {"max_tokens": 1024, "messages": [{"role":
81 | "user", "content": "What is the capital of the United States?"}], "model": "claude-3-haiku-20240307"},
82 | "tags": null, "request_response": {"id": "msg_01TYd9en4fxUtB11teUYmYqv", "content":
83 | [{"citations": null, "text": "The capital of the United States is Washington,
84 | D.C.", "type": "text"}], "model": "claude-3-haiku-20240307", "role": "assistant",
85 | "stop_reason": "end_turn", "stop_sequence": null, "type": "message", "usage":
86 | {"cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "input_tokens":
87 | 16, "output_tokens": 16}}, "request_start_time": 1744628887.82317, "request_end_time":
88 | 1744628888.853475, "metadata": null, "span_id": null, "api_key": "sanitized"}'
89 | headers:
90 | Accept:
91 | - '*/*'
92 | Accept-Encoding:
93 | - gzip, deflate
94 | Connection:
95 | - keep-alive
96 | Content-Length:
97 | - '848'
98 | Content-Type:
99 | - application/json
100 | User-Agent:
101 | - python-requests/2.31.0
102 | method: POST
103 | uri: http://localhost:8000/track-request
104 | response:
105 | body:
106 | string: '{"success":true,"request_id":133,"prompt_blueprint":null,"message":"Request
107 | tracked successfully"}
108 |
109 | '
110 | headers:
111 | Connection:
112 | - close
113 | Content-Length:
114 | - '99'
115 | Content-Type:
116 | - application/json
117 | Date:
118 | - Mon, 14 Apr 2025 11:08:08 GMT
119 | Server:
120 | - gunicorn
121 | status:
122 | code: 200
123 | message: OK
124 | version: 1
125 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_anthropic_chat_completion_async.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "What is
4 | the capital of Spain?"}], "model": "claude-3-haiku-20240307"}'
5 | headers:
6 | accept:
7 | - application/json
8 | accept-encoding:
9 | - gzip, deflate
10 | anthropic-version:
11 | - '2023-06-01'
12 | connection:
13 | - keep-alive
14 | content-length:
15 | - '124'
16 | content-type:
17 | - application/json
18 | host:
19 | - api.anthropic.com
20 | user-agent:
21 | - AsyncAnthropic/Python 0.49.0
22 | x-api-key:
23 | - sanitized
24 | x-stainless-arch:
25 | - x64
26 | x-stainless-async:
27 | - async:asyncio
28 | x-stainless-lang:
29 | - python
30 | x-stainless-os:
31 | - Linux
32 | x-stainless-package-version:
33 | - 0.49.0
34 | x-stainless-read-timeout:
35 | - '600'
36 | x-stainless-retry-count:
37 | - '0'
38 | x-stainless-runtime:
39 | - CPython
40 | x-stainless-runtime-version:
41 | - 3.9.21
42 | x-stainless-timeout:
43 | - '600'
44 | method: POST
45 | uri: https://api.anthropic.com/v1/messages
46 | response:
47 | body:
48 | string: '{"id":"msg_0196dGvnCAKhToSuS6jPSoNb","type":"message","role":"assistant","model":"claude-3-haiku-20240307","content":[{"type":"text","text":"The
49 | capital of Spain is Madrid."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":14,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":10}}'
50 | headers:
51 | CF-RAY:
52 | - 9302cb474e6cec4b-DME
53 | Connection:
54 | - keep-alive
55 | Content-Type:
56 | - application/json
57 | Date:
58 | - Mon, 14 Apr 2025 11:16:58 GMT
59 | Server:
60 | - cloudflare
61 | Transfer-Encoding:
62 | - chunked
63 | X-Robots-Tag:
64 | - none
65 | anthropic-organization-id:
66 | - ec8f80fa-3527-4d15-8ab4-526e6a1f4899
67 | cf-cache-status:
68 | - DYNAMIC
69 | content-length:
70 | - '329'
71 | request-id:
72 | - req_018YSc9VH7zK3pRPS7rfNcN9
73 | via:
74 | - 1.1 google
75 | status:
76 | code: 200
77 | message: OK
78 | - request:
79 | body: '{"function_name": "anthropic.AsyncAnthropic.messages.create", "provider_type":
80 | "anthropic", "args": [], "kwargs": {"max_tokens": 1024, "messages": [{"role":
81 | "user", "content": "What is the capital of Spain?"}], "model": "claude-3-haiku-20240307"},
82 | "tags": null, "request_response": {"id": "msg_0196dGvnCAKhToSuS6jPSoNb", "content":
83 | [{"citations": null, "text": "The capital of Spain is Madrid.", "type": "text"}],
84 | "model": "claude-3-haiku-20240307", "role": "assistant", "stop_reason": "end_turn",
85 | "stop_sequence": null, "type": "message", "usage": {"cache_creation_input_tokens":
86 | 0, "cache_read_input_tokens": 0, "input_tokens": 14, "output_tokens": 10}},
87 | "request_start_time": 1744629417.539952, "request_end_time": 1744629418.695542,
88 | "metadata": null, "span_id": null, "api_key": "sanitized"}'
89 | headers:
90 | Accept:
91 | - '*/*'
92 | Accept-Encoding:
93 | - gzip, deflate
94 | Connection:
95 | - keep-alive
96 | Content-Length:
97 | - '821'
98 | Content-Type:
99 | - application/json
100 | User-Agent:
101 | - python-requests/2.31.0
102 | method: POST
103 | uri: http://localhost:8000/track-request
104 | response:
105 | body:
106 | string: '{"success":true,"request_id":137,"prompt_blueprint":null,"message":"Request
107 | tracked successfully"}
108 |
109 | '
110 | headers:
111 | Connection:
112 | - close
113 | Content-Length:
114 | - '99'
115 | Content-Type:
116 | - application/json
117 | Date:
118 | - Mon, 14 Apr 2025 11:16:58 GMT
119 | Server:
120 | - gunicorn
121 | status:
122 | code: 200
123 | message: OK
124 | version: 1
125 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_anthropic_chat_completion_async_stream_with_pl_id.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "What is
4 | the capital of Japan?"}], "model": "claude-3-haiku-20240307", "stream": true}'
5 | headers:
6 | accept:
7 | - application/json
8 | accept-encoding:
9 | - gzip, deflate
10 | anthropic-version:
11 | - '2023-06-01'
12 | connection:
13 | - keep-alive
14 | content-length:
15 | - '138'
16 | content-type:
17 | - application/json
18 | host:
19 | - api.anthropic.com
20 | user-agent:
21 | - AsyncAnthropic/Python 0.49.0
22 | x-api-key:
23 | - sanitized
24 | x-stainless-arch:
25 | - x64
26 | x-stainless-async:
27 | - async:asyncio
28 | x-stainless-lang:
29 | - python
30 | x-stainless-os:
31 | - Linux
32 | x-stainless-package-version:
33 | - 0.49.0
34 | x-stainless-read-timeout:
35 | - '600'
36 | x-stainless-retry-count:
37 | - '0'
38 | x-stainless-runtime:
39 | - CPython
40 | x-stainless-runtime-version:
41 | - 3.9.21
42 | x-stainless-timeout:
43 | - NOT_GIVEN
44 | method: POST
45 | uri: https://api.anthropic.com/v1/messages
46 | response:
47 | body:
48 | string: 'event: message_start
49 |
50 | data: {"type":"message_start","message":{"id":"msg_01Bi6S5crUgtL7PUCuYc8Vy6","type":"message","role":"assistant","model":"claude-3-haiku-20240307","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":14,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":4}} }
51 |
52 |
53 | event: content_block_start
54 |
55 | data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""} }
56 |
57 |
58 | event: ping
59 |
60 | data: {"type": "ping"}
61 |
62 |
63 | event: content_block_delta
64 |
65 | data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"The
66 | capital of Japan"} }
67 |
68 |
69 | event: content_block_delta
70 |
71 | data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"
72 | is Tokyo."} }
73 |
74 |
75 | event: content_block_stop
76 |
77 | data: {"type":"content_block_stop","index":0 }
78 |
79 |
80 | event: message_delta
81 |
82 | data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":10} }
83 |
84 |
85 | event: message_stop
86 |
87 | data: {"type":"message_stop" }
88 |
89 |
90 | '
91 | headers:
92 | CF-RAY:
93 | - 930ce0917db19dc1-DME
94 | Cache-Control:
95 | - no-cache
96 | Connection:
97 | - keep-alive
98 | Content-Type:
99 | - text/event-stream; charset=utf-8
100 | Date:
101 | - Tue, 15 Apr 2025 16:39:08 GMT
102 | Server:
103 | - cloudflare
104 | Transfer-Encoding:
105 | - chunked
106 | X-Robots-Tag:
107 | - none
108 | anthropic-organization-id:
109 | - ec8f80fa-3527-4d15-8ab4-526e6a1f4899
110 | cf-cache-status:
111 | - DYNAMIC
112 | request-id:
113 | - req_01QoFNrnhsQbRcsukasRMqQ7
114 | via:
115 | - 1.1 google
116 | status:
117 | code: 200
118 | message: OK
119 | - request:
120 | body: '{"function_name": "anthropic.AsyncAnthropic.messages.create", "provider_type":
121 | "anthropic", "args": [], "kwargs": {"max_tokens": 1024, "messages": [{"role":
122 | "user", "content": "What is the capital of Japan?"}], "model": "claude-3-haiku-20240307",
123 | "stream": true}, "tags": null, "request_response": {"id": "msg_01Bi6S5crUgtL7PUCuYc8Vy6",
124 | "content": [{"citations": null, "text": "The capital of Japan is Tokyo.", "type":
125 | "text"}], "model": "claude-3-haiku-20240307", "role": "assistant", "stop_reason":
126 | null, "stop_sequence": null, "type": "message", "usage": null}, "request_start_time":
127 | 1744735147.208988, "request_end_time": 1744735148.237783, "metadata": null,
128 | "span_id": null, "api_key": "sanitized"}'
129 | headers:
130 | Accept:
131 | - '*/*'
132 | Accept-Encoding:
133 | - gzip, deflate
134 | Connection:
135 | - keep-alive
136 | Content-Length:
137 | - '729'
138 | Content-Type:
139 | - application/json
140 | User-Agent:
141 | - python-requests/2.31.0
142 | method: POST
143 | uri: http://localhost:8000/track-request
144 | response:
145 | body:
146 | string: '{"success":true,"request_id":191,"prompt_blueprint":null,"message":"Request
147 | tracked successfully"}
148 |
149 | '
150 | headers:
151 | Connection:
152 | - close
153 | Content-Length:
154 | - '99'
155 | Content-Type:
156 | - application/json
157 | Date:
158 | - Tue, 15 Apr 2025 16:39:08 GMT
159 | Server:
160 | - gunicorn
161 | status:
162 | code: 200
163 | message: OK
164 | version: 1
165 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_anthropic_chat_completion_with_pl_id.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "What is
4 | the capital of France?"}], "model": "claude-3-haiku-20240307"}'
5 | headers:
6 | accept:
7 | - application/json
8 | accept-encoding:
9 | - gzip, deflate
10 | anthropic-version:
11 | - '2023-06-01'
12 | connection:
13 | - keep-alive
14 | content-length:
15 | - '125'
16 | content-type:
17 | - application/json
18 | host:
19 | - api.anthropic.com
20 | user-agent:
21 | - Anthropic/Python 0.49.0
22 | x-api-key:
23 | - sanitized
24 | x-stainless-arch:
25 | - x64
26 | x-stainless-async:
27 | - 'false'
28 | x-stainless-lang:
29 | - python
30 | x-stainless-os:
31 | - Linux
32 | x-stainless-package-version:
33 | - 0.49.0
34 | x-stainless-read-timeout:
35 | - '600'
36 | x-stainless-retry-count:
37 | - '0'
38 | x-stainless-runtime:
39 | - CPython
40 | x-stainless-runtime-version:
41 | - 3.9.21
42 | x-stainless-timeout:
43 | - '600'
44 | method: POST
45 | uri: https://api.anthropic.com/v1/messages
46 | response:
47 | body:
48 | string: '{"id":"msg_01BTdpWGHBkDbTHb7MS95DLy","type":"message","role":"assistant","model":"claude-3-haiku-20240307","content":[{"type":"text","text":"The
49 | capital of France is Paris."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":14,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":10}}'
50 | headers:
51 | CF-RAY:
52 | - 9302ca74ec41e317-DME
53 | Connection:
54 | - keep-alive
55 | Content-Type:
56 | - application/json
57 | Date:
58 | - Mon, 14 Apr 2025 11:16:24 GMT
59 | Server:
60 | - cloudflare
61 | Transfer-Encoding:
62 | - chunked
63 | X-Robots-Tag:
64 | - none
65 | anthropic-organization-id:
66 | - ec8f80fa-3527-4d15-8ab4-526e6a1f4899
67 | cf-cache-status:
68 | - DYNAMIC
69 | content-length:
70 | - '329'
71 | request-id:
72 | - req_01YYd3cXehJ6bHEGH5ne1AJZ
73 | via:
74 | - 1.1 google
75 | status:
76 | code: 200
77 | message: OK
78 | - request:
79 | body: '{"function_name": "anthropic.Anthropic.messages.create", "provider_type":
80 | "anthropic", "args": [], "kwargs": {"max_tokens": 1024, "messages": [{"role":
81 | "user", "content": "What is the capital of France?"}], "model": "claude-3-haiku-20240307"},
82 | "tags": null, "request_response": {"id": "msg_01BTdpWGHBkDbTHb7MS95DLy", "content":
83 | [{"citations": null, "text": "The capital of France is Paris.", "type": "text"}],
84 | "model": "claude-3-haiku-20240307", "role": "assistant", "stop_reason": "end_turn",
85 | "stop_sequence": null, "type": "message", "usage": {"cache_creation_input_tokens":
86 | 0, "cache_read_input_tokens": 0, "input_tokens": 14, "output_tokens": 10}},
87 | "request_start_time": 1744629384.010934, "request_end_time": 1744629384.909616,
88 | "metadata": null, "span_id": null, "api_key": "sanitized"}'
89 | headers:
90 | Accept:
91 | - '*/*'
92 | Accept-Encoding:
93 | - gzip, deflate
94 | Connection:
95 | - keep-alive
96 | Content-Length:
97 | - '817'
98 | Content-Type:
99 | - application/json
100 | User-Agent:
101 | - python-requests/2.31.0
102 | method: POST
103 | uri: http://localhost:8000/track-request
104 | response:
105 | body:
106 | string: '{"success":true,"request_id":134,"prompt_blueprint":null,"message":"Request
107 | tracked successfully"}
108 |
109 | '
110 | headers:
111 | Connection:
112 | - close
113 | Content-Length:
114 | - '99'
115 | Content-Type:
116 | - application/json
117 | Date:
118 | - Mon, 14 Apr 2025 11:16:24 GMT
119 | Server:
120 | - gunicorn
121 | status:
122 | code: 200
123 | message: OK
124 | version: 1
125 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_anthropic_chat_completion_with_stream.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "What is
4 | the capital of Germany?"}], "model": "claude-3-haiku-20240307", "stream": true}'
5 | headers:
6 | accept:
7 | - application/json
8 | accept-encoding:
9 | - gzip, deflate
10 | anthropic-version:
11 | - '2023-06-01'
12 | connection:
13 | - keep-alive
14 | content-length:
15 | - '140'
16 | content-type:
17 | - application/json
18 | host:
19 | - api.anthropic.com
20 | user-agent:
21 | - Anthropic/Python 0.49.0
22 | x-api-key:
23 | - sanitized
24 | x-stainless-arch:
25 | - x64
26 | x-stainless-async:
27 | - 'false'
28 | x-stainless-lang:
29 | - python
30 | x-stainless-os:
31 | - Linux
32 | x-stainless-package-version:
33 | - 0.49.0
34 | x-stainless-read-timeout:
35 | - '600'
36 | x-stainless-retry-count:
37 | - '0'
38 | x-stainless-runtime:
39 | - CPython
40 | x-stainless-runtime-version:
41 | - 3.9.21
42 | x-stainless-timeout:
43 | - NOT_GIVEN
44 | method: POST
45 | uri: https://api.anthropic.com/v1/messages
46 | response:
47 | body:
48 | string: 'event: message_start
49 |
50 | data: {"type":"message_start","message":{"id":"msg_01PP15qoPAWehXLzXosCnnVP","type":"message","role":"assistant","model":"claude-3-haiku-20240307","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":14,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":4}} }
51 |
52 |
53 | event: content_block_start
54 |
55 | data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""} }
56 |
57 |
58 | event: ping
59 |
60 | data: {"type": "ping"}
61 |
62 |
63 | event: content_block_delta
64 |
65 | data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"The
66 | capital of Germany"} }
67 |
68 |
69 | event: content_block_delta
70 |
71 | data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"
72 | is Berlin."} }
73 |
74 |
75 | event: content_block_stop
76 |
77 | data: {"type":"content_block_stop","index":0 }
78 |
79 |
80 | event: message_delta
81 |
82 | data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":10} }
83 |
84 |
85 | event: message_stop
86 |
87 | data: {"type":"message_stop" }
88 |
89 |
90 | '
91 | headers:
92 | CF-RAY:
93 | - 930cabf0da58f112-DME
94 | Cache-Control:
95 | - no-cache
96 | Connection:
97 | - keep-alive
98 | Content-Type:
99 | - text/event-stream; charset=utf-8
100 | Date:
101 | - Tue, 15 Apr 2025 16:03:12 GMT
102 | Server:
103 | - cloudflare
104 | Transfer-Encoding:
105 | - chunked
106 | X-Robots-Tag:
107 | - none
108 | anthropic-organization-id:
109 | - ec8f80fa-3527-4d15-8ab4-526e6a1f4899
110 | cf-cache-status:
111 | - DYNAMIC
112 | request-id:
113 | - req_01KZGzAirTUtQ7vqZk3uEFB9
114 | via:
115 | - 1.1 google
116 | status:
117 | code: 200
118 | message: OK
119 | - request:
120 | body: '{"function_name": "anthropic.Anthropic.messages.create", "provider_type":
121 | "anthropic", "args": [], "kwargs": {"max_tokens": 1024, "messages": [{"role":
122 | "user", "content": "What is the capital of Germany?"}], "model": "claude-3-haiku-20240307",
123 | "stream": true}, "tags": null, "request_response": {"id": "msg_01PP15qoPAWehXLzXosCnnVP",
124 | "content": [{"citations": null, "text": "The capital of Germany is Berlin.",
125 | "type": "text"}], "model": "claude-3-haiku-20240307", "role": "assistant", "stop_reason":
126 | null, "stop_sequence": null, "type": "message", "usage": null}, "request_start_time":
127 | 1744732991.599045, "request_end_time": 1744732992.605533, "metadata": null,
128 | "span_id": null, "api_key": "sanitized"}'
129 | headers:
130 | Accept:
131 | - '*/*'
132 | Accept-Encoding:
133 | - gzip, deflate
134 | Connection:
135 | - keep-alive
136 | Content-Length:
137 | - '729'
138 | Content-Type:
139 | - application/json
140 | User-Agent:
141 | - python-requests/2.31.0
142 | method: POST
143 | uri: http://localhost:8000/track-request
144 | response:
145 | body:
146 | string: '{"success":true,"request_id":176,"prompt_blueprint":null,"message":"Request
147 | tracked successfully"}
148 |
149 | '
150 | headers:
151 | Connection:
152 | - close
153 | Content-Length:
154 | - '99'
155 | Content-Type:
156 | - application/json
157 | Date:
158 | - Tue, 15 Apr 2025 16:03:12 GMT
159 | Server:
160 | - gunicorn
161 | status:
162 | code: 200
163 | message: OK
164 | version: 1
165 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_anthropic_chat_completion_with_stream_and_pl_id.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "What is
4 | the capital of Italy?"}], "model": "claude-3-haiku-20240307", "stream": true}'
5 | headers:
6 | accept:
7 | - application/json
8 | accept-encoding:
9 | - gzip, deflate
10 | anthropic-version:
11 | - '2023-06-01'
12 | connection:
13 | - keep-alive
14 | content-length:
15 | - '138'
16 | content-type:
17 | - application/json
18 | host:
19 | - api.anthropic.com
20 | user-agent:
21 | - Anthropic/Python 0.49.0
22 | x-api-key:
23 | - sanitized
24 | x-stainless-arch:
25 | - x64
26 | x-stainless-async:
27 | - 'false'
28 | x-stainless-lang:
29 | - python
30 | x-stainless-os:
31 | - Linux
32 | x-stainless-package-version:
33 | - 0.49.0
34 | x-stainless-read-timeout:
35 | - '600'
36 | x-stainless-retry-count:
37 | - '0'
38 | x-stainless-runtime:
39 | - CPython
40 | x-stainless-runtime-version:
41 | - 3.9.21
42 | x-stainless-timeout:
43 | - NOT_GIVEN
44 | method: POST
45 | uri: https://api.anthropic.com/v1/messages
46 | response:
47 | body:
48 | string: 'event: message_start
49 |
50 | data: {"type":"message_start","message":{"id":"msg_016q2kSZ82qtDP2CNUAKSfLV","type":"message","role":"assistant","model":"claude-3-haiku-20240307","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":14,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":4}} }
51 |
52 |
53 | event: content_block_start
54 |
55 | data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""} }
56 |
57 |
58 | event: ping
59 |
60 | data: {"type": "ping"}
61 |
62 |
63 | event: content_block_delta
64 |
65 | data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"The
66 | capital of Italy"} }
67 |
68 |
69 | event: content_block_delta
70 |
71 | data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"
72 | is Rome."} }
73 |
74 |
75 | event: content_block_stop
76 |
77 | data: {"type":"content_block_stop","index":0 }
78 |
79 |
80 | event: message_delta
81 |
82 | data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":10} }
83 |
84 |
85 | event: message_stop
86 |
87 | data: {"type":"message_stop" }
88 |
89 |
90 | '
91 | headers:
92 | CF-RAY:
93 | - 930ca449f9c29dd6-DME
94 | Cache-Control:
95 | - no-cache
96 | Connection:
97 | - keep-alive
98 | Content-Type:
99 | - text/event-stream; charset=utf-8
100 | Date:
101 | - Tue, 15 Apr 2025 15:57:59 GMT
102 | Server:
103 | - cloudflare
104 | Transfer-Encoding:
105 | - chunked
106 | X-Robots-Tag:
107 | - none
108 | anthropic-organization-id:
109 | - ec8f80fa-3527-4d15-8ab4-526e6a1f4899
110 | cf-cache-status:
111 | - DYNAMIC
112 | request-id:
113 | - req_01MhQVkzE2Dj5LJ9hwtn94A2
114 | via:
115 | - 1.1 google
116 | status:
117 | code: 200
118 | message: OK
119 | - request:
120 | body: '{"function_name": "anthropic.Anthropic.messages.create", "provider_type":
121 | "anthropic", "args": [], "kwargs": {"max_tokens": 1024, "messages": [{"role":
122 | "user", "content": "What is the capital of Italy?"}], "model": "claude-3-haiku-20240307",
123 | "stream": true}, "tags": null, "request_response": {"id": "msg_016q2kSZ82qtDP2CNUAKSfLV",
124 | "content": [{"citations": null, "text": "The capital of Italy is Rome.", "type":
125 | "text"}], "model": "claude-3-haiku-20240307", "role": "assistant", "stop_reason":
126 | null, "stop_sequence": null, "type": "message", "usage": null}, "request_start_time":
127 | 1744732678.298123, "request_end_time": 1744732679.148348, "metadata": null,
128 | "span_id": null, "api_key": "sanitized"}'
129 | headers:
130 | Accept:
131 | - '*/*'
132 | Accept-Encoding:
133 | - gzip, deflate
134 | Connection:
135 | - keep-alive
136 | Content-Length:
137 | - '723'
138 | Content-Type:
139 | - application/json
140 | User-Agent:
141 | - python-requests/2.31.0
142 | method: POST
143 | uri: http://localhost:8000/track-request
144 | response:
145 | body:
146 | string: '{"success":true,"request_id":175,"prompt_blueprint":null,"message":"Request
147 | tracked successfully"}
148 |
149 | '
150 | headers:
151 | Connection:
152 | - close
153 | Content-Length:
154 | - '99'
155 | Content-Type:
156 | - application/json
157 | Date:
158 | - Tue, 15 Apr 2025 15:57:59 GMT
159 | Server:
160 | - gunicorn
161 | status:
162 | code: 200
163 | message: OK
164 | version: 1
165 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_arun_workflow_request.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: ''
4 | headers:
5 | accept:
6 | - '*/*'
7 | accept-encoding:
8 | - gzip, deflate
9 | connection:
10 | - keep-alive
11 | host:
12 | - localhost:8000
13 | user-agent:
14 | - python-httpx/0.28.1
15 | x-api-key:
16 | - sanitized
17 | method: GET
18 | uri: http://localhost:8000/workflows/analyze_1
19 | response:
20 | body:
21 | string: '{"success":true,"workflow":{"id":3,"workspace_id":1,"user_id":null,"name":"analyze_1","is_draft":false,"is_deleted":false}}
22 |
23 | '
24 | headers:
25 | Connection:
26 | - close
27 | Content-Length:
28 | - '124'
29 | Content-Type:
30 | - application/json
31 | Date:
32 | - Thu, 01 May 2025 16:10:30 GMT
33 | Server:
34 | - gunicorn
35 | status:
36 | code: 200
37 | message: OK
38 | - request:
39 | body: ''
40 | headers:
41 | accept:
42 | - '*/*'
43 | accept-encoding:
44 | - gzip, deflate
45 | connection:
46 | - keep-alive
47 | content-length:
48 | - '0'
49 | host:
50 | - localhost:8000
51 | user-agent:
52 | - python-httpx/0.28.1
53 | x-api-key:
54 | - sanitized
55 | method: POST
56 | uri: http://localhost:8000/ws-token-request-library?capability=workflows%3A3%3Arun%3A8dd7e4d404754c60a50e78f70f74aade
57 | response:
58 | body:
59 | string: '{"success":true,"token_details":{"expires":1746119430452,"token":"uV0vXQ.JAcrUQfVqw-T7-Rlm000FrnEV3UAXYba0goN5h9XPQ7OrNfb5uq1juDZjNTFUWP9T6Gvvt3a0zzVfyuud8s9_uUFWB5Xu5pFLkbrAq1Le9BVvUhhCDdmOPzZVwKZRxJgRBQw7A7o6AABMcItiaennjFKUltTGmawIsX3kCEyBRiNluPC0k5SRhXTPfIC1gidZ3PZGHNB-PN1IDxqZVBlld5NwU2iLg-UQcvMR3fzRz-8SQQ9cDFp1HlEq38JNGvxA","issued":1746115830452,"capability":{"user:1":["presence","subscribe"],"workflows:3:run:8dd7e4d404754c60a50e78f70f74aade":["presence","subscribe"]},"clientId":"1"}}
60 |
61 | '
62 | headers:
63 | Connection:
64 | - close
65 | Content-Length:
66 | - '497'
67 | Content-Type:
68 | - application/json
69 | Date:
70 | - Thu, 01 May 2025 16:10:31 GMT
71 | Server:
72 | - gunicorn
73 | status:
74 | code: 201
75 | message: CREATED
76 | - request:
77 | body: '{"input_variables": {"var1": "value1"}, "metadata": null, "workflow_label_name":
78 | null, "workflow_version_number": null, "return_all_outputs": false, "channel_name_suffix":
79 | "8dd7e4d404754c60a50e78f70f74aade"}'
80 | headers:
81 | accept:
82 | - '*/*'
83 | accept-encoding:
84 | - gzip, deflate
85 | connection:
86 | - keep-alive
87 | content-length:
88 | - '195'
89 | content-type:
90 | - application/json
91 | host:
92 | - localhost:8000
93 | user-agent:
94 | - python-httpx/0.28.1
95 | x-api-key:
96 | - sanitized
97 | method: POST
98 | uri: http://localhost:8000/workflows/3/run
99 | response:
100 | body:
101 | string: '{"success":true,"message":"Workflow execution created successfully","warning":null,"workflow_version_execution_id":787}
102 |
103 | '
104 | headers:
105 | Connection:
106 | - close
107 | Content-Length:
108 | - '120'
109 | Content-Type:
110 | - application/json
111 | Date:
112 | - Thu, 01 May 2025 16:10:32 GMT
113 | Server:
114 | - gunicorn
115 | status:
116 | code: 201
117 | message: CREATED
118 | version: 1
119 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_get_all_templates.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: null
4 | headers:
5 | Accept:
6 | - '*/*'
7 | Accept-Encoding:
8 | - gzip, deflate
9 | Connection:
10 | - keep-alive
11 | User-Agent:
12 | - python-requests/2.31.0
13 | x-api-key:
14 | - sanitized
15 | method: GET
16 | uri: http://localhost:8000/prompt-templates?page=1&per_page=30
17 | response:
18 | body:
19 | string: '{"items":[{"id":4,"prompt_name":"sample_template","tags":["test"],"folder_id":null,"prompt_template":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":""}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"What
20 | is the capital of Japan?"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null}],"functions":[],"tools":null,"function_call":"none","tool_choice":null,"type":"chat","input_variables":[],"dataset_examples":[]},"version":1,"commit_message":"test","metadata":{"model":{"provider":"openai","name":"gpt-4o-mini","parameters":{"frequency_penalty":0,"max_tokens":256,"messages":[{"content":"Hello","role":"system"}],"model":"gpt-4o","presence_penalty":0,"seed":0,"temperature":1,"top_p":1}}},"parent_folder_name":null,"full_folder_path":null},{"id":3,"prompt_name":"simple","tags":[],"folder_id":null,"prompt_template":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"You
21 | are helpful assistant"}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"Reply
22 | with Hey"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null}],"functions":null,"tools":null,"function_call":null,"tool_choice":null,"type":"chat","input_variables":[],"dataset_examples":[]},"version":1,"commit_message":null,"metadata":null,"parent_folder_name":null,"full_folder_path":null},{"id":1,"prompt_name":"Example:
23 | page_title","tags":[],"folder_id":null,"prompt_template":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"You
24 | are a helpful AI assistant. You work for an online media organization called
25 | BuzzFeed.\n\nYour task is to create catchy article titles based on a summary.
26 | The titles should be no more than 10 words. The titles should have no emojis.\n\nThe
27 | goal is to write catchy & easy to write blog post titles. These titles should
28 | be \"clickbait\" and encourage users to click on the blog.\n\nThe user will
29 | provide a blog post abstract summary as well as a topic keyword. You will
30 | respond with a title. Don''t use quotes in the title."}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"#
31 | Topic\nTravel\n\n# Summary\nWanderlust calling? Check out our curated list
32 | of 10 awe-inspiring travel destinations that should be on every adventurer''s
33 | bucket list. From tropical paradises to historic cities, these places offer
34 | unforgettable experiences. Get ready to fuel your wanderlust and start planning
35 | your next getaway!"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"The
36 | Ultimate Travel Bucket List: 10 Breathtaking Destinations to Visit"}],"raw_request_display_role":"","dataset_examples":[],"role":"assistant","function_call":null,"name":null,"tool_calls":null},{"input_variables":["article","topic"],"template_format":"f-string","content":[{"type":"text","text":"#
37 | Topic\n{topic}\n\n# Summary\n{article}"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null}],"functions":null,"tools":null,"function_call":"none","tool_choice":null,"type":"chat","input_variables":["article","topic"],"dataset_examples":[]},"version":6,"commit_message":"No
38 | quotes, higher temp","metadata":{"model":{"provider":"openai","name":"gpt-3.5-turbo","parameters":{"best_of":1,"frequency_penalty":0,"max_tokens":256,"presence_penalty":0,"temperature":1.2,"top_p":1}}},"parent_folder_name":null,"full_folder_path":null}],"has_prev":false,"has_next":false,"prev_num":null,"next_num":null,"page":1,"pages":1,"per_page":30,"total":3}
39 |
40 | '
41 | headers:
42 | Connection:
43 | - close
44 | Content-Length:
45 | - '3977'
46 | Content-Type:
47 | - application/json
48 | Date:
49 | - Mon, 14 Apr 2025 10:53:02 GMT
50 | Server:
51 | - gunicorn
52 | status:
53 | code: 200
54 | message: OK
55 | version: 1
56 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_get_final_output_1.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: ''
4 | headers:
5 | accept:
6 | - '*/*'
7 | accept-encoding:
8 | - gzip, deflate
9 | connection:
10 | - keep-alive
11 | host:
12 | - localhost:8000
13 | user-agent:
14 | - python-httpx/0.28.1
15 | x-api-key:
16 | - sanitized
17 | method: GET
18 | uri: http://localhost:8000/workflow-version-execution-results?workflow_version_execution_id=717&return_all_outputs=true
19 | response:
20 | body:
21 | string: '{"Node 1":{"status":"SUCCESS","value":"AAA","error_message":null,"raw_error_message":null,"is_output_node":true}}
22 |
23 | '
24 | headers:
25 | Connection:
26 | - close
27 | Content-Length:
28 | - '114'
29 | Content-Type:
30 | - application/json
31 | Date:
32 | - Fri, 11 Apr 2025 17:44:48 GMT
33 | Server:
34 | - gunicorn
35 | status:
36 | code: 200
37 | message: OK
38 | version: 1
39 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_get_final_output_2.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: ''
4 | headers:
5 | accept:
6 | - '*/*'
7 | accept-encoding:
8 | - gzip, deflate
9 | connection:
10 | - keep-alive
11 | host:
12 | - localhost:8000
13 | user-agent:
14 | - python-httpx/0.28.1
15 | x-api-key:
16 | - sanitized
17 | method: GET
18 | uri: http://localhost:8000/workflow-version-execution-results?workflow_version_execution_id=717&return_all_outputs=false
19 | response:
20 | body:
21 | string: '"AAA"
22 |
23 | '
24 | headers:
25 | Connection:
26 | - close
27 | Content-Length:
28 | - '6'
29 | Content-Type:
30 | - application/json
31 | Date:
32 | - Fri, 11 Apr 2025 17:50:28 GMT
33 | Server:
34 | - gunicorn
35 | status:
36 | code: 200
37 | message: OK
38 | version: 1
39 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_get_prompt_template_provider_base_url_name.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"prompt_template": {"provider_base_url_name": "does_not_exist", "prompt_name":
4 | "test_template:test", "prompt_template": {"type": "chat", "provider_base_url_name":
5 | "does_not_exist", "messages": [{"content": [{"text": "You are an AI.", "type":
6 | "text"}], "input_variables": [], "name": null, "raw_request_display_role": "",
7 | "role": "system", "template_format": "f-string"}, {"content": [{"text": "What
8 | is the capital of Japan?", "type": "text"}], "input_variables": [], "name":
9 | null, "raw_request_display_role": "", "role": "user", "template_format": "f-string"}]}},
10 | "prompt_version": {"provider_base_url_name": "does_not_exist", "prompt_name":
11 | "test_template:test", "prompt_template": {"type": "chat", "provider_base_url_name":
12 | "does_not_exist", "messages": [{"content": [{"text": "You are an AI.", "type":
13 | "text"}], "input_variables": [], "name": null, "raw_request_display_role": "",
14 | "role": "system", "template_format": "f-string"}, {"content": [{"text": "What
15 | is the capital of Japan?", "type": "text"}], "input_variables": [], "name":
16 | null, "raw_request_display_role": "", "role": "user", "template_format": "f-string"}]}},
17 | "release_labels": null}'
18 | headers:
19 | Accept:
20 | - '*/*'
21 | Accept-Encoding:
22 | - gzip, deflate
23 | Connection:
24 | - keep-alive
25 | Content-Length:
26 | - '1151'
27 | Content-Type:
28 | - application/json
29 | User-Agent:
30 | - python-requests/2.31.0
31 | x-api-key:
32 | - sanitized
33 | method: POST
34 | uri: http://localhost:8000/rest/prompt-templates
35 | response:
36 | body:
37 | string: '{"id":5,"prompt_name":"test_template:test","tags":[],"prompt_template":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"You
38 | are an AI."}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"What
39 | is the capital of Japan?"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null}],"functions":null,"tools":null,"function_call":null,"tool_choice":null,"type":"chat","input_variables":[],"dataset_examples":[]},"commit_message":null,"metadata":null,"release_labels":null}
40 |
41 | '
42 | headers:
43 | Connection:
44 | - close
45 | Content-Length:
46 | - '655'
47 | Content-Type:
48 | - application/json
49 | Date:
50 | - Mon, 14 Apr 2025 11:27:33 GMT
51 | Server:
52 | - gunicorn
53 | status:
54 | code: 201
55 | message: CREATED
56 | - request:
57 | body: '{"provider": "openai", "model": "gpt-3.5-turbo", "api_key": "sanitized"}'
58 | headers:
59 | Accept:
60 | - '*/*'
61 | Accept-Encoding:
62 | - gzip, deflate
63 | Connection:
64 | - keep-alive
65 | Content-Length:
66 | - '98'
67 | Content-Type:
68 | - application/json
69 | User-Agent:
70 | - python-requests/2.31.0
71 | x-api-key:
72 | - sanitized
73 | method: POST
74 | uri: http://localhost:8000/prompt-templates/test_template:test
75 | response:
76 | body:
77 | string: '{"id":5,"prompt_name":"test_template:test","tags":[],"workspace_id":1,"commit_message":null,"metadata":null,"prompt_template":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"You
78 | are an AI."}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"What
79 | is the capital of Japan?"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null}],"functions":null,"tools":null,"function_call":null,"tool_choice":null,"type":"chat","input_variables":[],"dataset_examples":[]},"llm_kwargs":{"messages":[{"role":"system","content":"You
80 | are an AI."},{"role":"user","content":[{"type":"text","text":"What is the
81 | capital of Japan?"}]}]},"provider_base_url":null,"version":1,"snippets":[],"warning":null}
82 |
83 | '
84 | headers:
85 | Connection:
86 | - close
87 | Content-Length:
88 | - '872'
89 | Content-Type:
90 | - application/json
91 | Date:
92 | - Mon, 14 Apr 2025 11:27:33 GMT
93 | Server:
94 | - gunicorn
95 | status:
96 | code: 200
97 | message: OK
98 | version: 1
99 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_get_template_async.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"provider": "openai", "model": "gpt-3.5-turbo", "api_key": "sanitized"}'
4 | headers:
5 | accept:
6 | - '*/*'
7 | accept-encoding:
8 | - gzip, deflate
9 | connection:
10 | - keep-alive
11 | content-length:
12 | - '93'
13 | content-type:
14 | - application/json
15 | host:
16 | - localhost:8000
17 | user-agent:
18 | - python-httpx/0.28.1
19 | x-api-key:
20 | - sanitized
21 | method: POST
22 | uri: http://localhost:8000/prompt-templates/sample_template
23 | response:
24 | body:
25 | string: '{"id":4,"prompt_name":"sample_template","tags":["test"],"workspace_id":1,"commit_message":"test","metadata":{"model":{"provider":"openai","name":"gpt-4o-mini","parameters":{"frequency_penalty":0,"max_tokens":256,"messages":[{"content":"Hello","role":"system"}],"model":"gpt-4o","presence_penalty":0,"seed":0,"temperature":1,"top_p":1}}},"prompt_template":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":""}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"What
26 | is the capital of Japan?"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null}],"functions":[],"tools":null,"function_call":"none","tool_choice":null,"type":"chat","input_variables":[],"dataset_examples":[]},"llm_kwargs":{"messages":[{"content":"Hello","role":"system"}],"model":"gpt-4o","frequency_penalty":0,"max_tokens":256,"presence_penalty":0,"seed":0,"temperature":1,"top_p":1},"provider_base_url":null,"version":1,"snippets":[],"warning":null}
27 |
28 | '
29 | headers:
30 | Connection:
31 | - close
32 | Content-Length:
33 | - '1107'
34 | Content-Type:
35 | - application/json
36 | Date:
37 | - Mon, 14 Apr 2025 09:33:23 GMT
38 | Server:
39 | - gunicorn
40 | status:
41 | code: 200
42 | message: OK
43 | version: 1
44 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_log_request_async.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"api_key": "sanitized"}'
4 | headers:
5 | accept:
6 | - '*/*'
7 | accept-encoding:
8 | - gzip, deflate
9 | connection:
10 | - keep-alive
11 | content-length:
12 | - '49'
13 | content-type:
14 | - application/json
15 | host:
16 | - localhost:8000
17 | user-agent:
18 | - python-httpx/0.28.1
19 | x-api-key:
20 | - sanitized
21 | method: POST
22 | uri: http://localhost:8000/prompt-templates/sample_template
23 | response:
24 | body:
25 | string: '{"id":4,"prompt_name":"sample_template","tags":["test"],"workspace_id":1,"commit_message":"test","metadata":{"model":{"provider":"openai","name":"gpt-4o-mini","parameters":{"frequency_penalty":0,"max_tokens":256,"messages":[{"content":"Hello","role":"system"}],"model":"gpt-4o","presence_penalty":0,"seed":0,"temperature":1,"top_p":1}}},"prompt_template":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":""}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"What
26 | is the capital of Japan?"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null}],"functions":[],"tools":null,"function_call":"none","tool_choice":null,"type":"chat","input_variables":[],"dataset_examples":[]},"llm_kwargs":{"messages":[{"content":"Hello","role":"system"}],"model":"gpt-4o","frequency_penalty":0,"max_tokens":256,"presence_penalty":0,"seed":0,"temperature":1,"top_p":1},"provider_base_url":null,"version":1,"snippets":[],"warning":null}
27 |
28 | '
29 | headers:
30 | Connection:
31 | - close
32 | Content-Length:
33 | - '1107'
34 | Content-Type:
35 | - application/json
36 | Date:
37 | - Mon, 14 Apr 2025 10:21:52 GMT
38 | Server:
39 | - gunicorn
40 | status:
41 | code: 200
42 | message: OK
43 | - request:
44 | body: '{"provider": "openai", "model": "gpt-4-mini", "input": {"messages": [{"input_variables":
45 | [], "template_format": "f-string", "content": [{"type": "text", "text": ""}],
46 | "raw_request_display_role": "", "dataset_examples": [], "role": "system", "name":
47 | null}, {"input_variables": [], "template_format": "f-string", "content": [{"type":
48 | "text", "text": "What is the capital of Japan?"}], "raw_request_display_role":
49 | "", "dataset_examples": [], "role": "user", "name": null}], "functions": [],
50 | "tools": null, "function_call": "none", "tool_choice": null, "type": "chat",
51 | "input_variables": [], "dataset_examples": []}, "output": {"messages": [{"input_variables":
52 | [], "template_format": "f-string", "content": [{"type": "text", "text": ""}],
53 | "raw_request_display_role": "", "dataset_examples": [], "role": "system", "name":
54 | null}, {"input_variables": [], "template_format": "f-string", "content": [{"type":
55 | "text", "text": "What is the capital of Japan?"}], "raw_request_display_role":
56 | "", "dataset_examples": [], "role": "user", "name": null}], "functions": [],
57 | "tools": null, "function_call": "none", "tool_choice": null, "type": "chat",
58 | "input_variables": [], "dataset_examples": []}, "request_start_time": 1744626112.7601638,
59 | "request_end_time": 1744626112.7601643, "parameters": {}, "tags": [], "metadata":
60 | {}, "prompt_name": null, "prompt_version_number": null, "prompt_input_variables":
61 | {}, "input_tokens": 0, "output_tokens": 0, "price": 0.0, "function_name": "",
62 | "score": 0}'
63 | headers:
64 | accept:
65 | - '*/*'
66 | accept-encoding:
67 | - gzip, deflate
68 | connection:
69 | - keep-alive
70 | content-length:
71 | - '1347'
72 | content-type:
73 | - application/json
74 | host:
75 | - localhost:8000
76 | user-agent:
77 | - python-httpx/0.28.1
78 | x-api-key:
79 | - sanitized
80 | method: POST
81 | uri: http://localhost:8000/log-request
82 | response:
83 | body:
84 | string: '{"id":130,"user_id":1,"prompt_id":null,"prompt_version_number":null,"prompt_input_variables":{},"provider_type":"openai","function_name":"","function_args":[],"function_kwargs":{"input":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":""}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"What
85 | is the capital of Japan?"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null}],"functions":[],"tools":null,"function_call":"none","tool_choice":null,"type":"chat","input_variables":[],"dataset_examples":[]},"parameters":{}},"request_response":{"output":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":""}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"What
86 | is the capital of Japan?"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null}],"functions":[],"tools":null,"function_call":"none","tool_choice":null,"type":"chat","input_variables":[],"dataset_examples":[]}},"request_start_time":"2025-04-14T10:21:52.760164","request_end_time":"2025-04-14T10:21:52.760164","is_sharable":false,"share_hash":"561672cb688836ec113a19469a75aa40","share_view_count":0,"is_starred":false,"price":0.0,"score":0,"engine":"gpt-4-mini","tokens":0,"input_tokens":0,"output_tokens":0,"workspace_id":1,"span_id":null,"created_at":"Mon,
87 | 14 Apr 2025 10:21:52 GMT","metadata":[],"scores":[],"type":"request","tags_array":[],"prompt_string":"","response_string":"What
88 | is the capital of Japan?","prompt_version":{"prompt_template":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":""}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"What
89 | is the capital of Japan?"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":""}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"What
90 | is the capital of Japan?"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null}],"functions":[],"tools":null,"function_call":"none","tool_choice":null,"type":"chat","input_variables":[],"dataset_examples":[]},"commit_message":null,"metadata":{"model":{"provider":"openai","name":"gpt-4-mini","parameters":{}}},"provider_base_url_name":null,"report_id":null,"inference_client_name":null}}
91 |
92 | '
93 | headers:
94 | Connection:
95 | - close
96 | Content-Length:
97 | - '2843'
98 | Content-Type:
99 | - application/json
100 | Date:
101 | - Mon, 14 Apr 2025 10:21:52 GMT
102 | Server:
103 | - gunicorn
104 | status:
105 | code: 201
106 | message: CREATED
107 | version: 1
108 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_make_message_listener_1.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: ''
4 | headers:
5 | accept:
6 | - '*/*'
7 | accept-encoding:
8 | - gzip, deflate
9 | connection:
10 | - keep-alive
11 | host:
12 | - localhost:8000
13 | user-agent:
14 | - python-httpx/0.28.1
15 | x-api-key:
16 | - sanitized
17 | method: GET
18 | uri: http://localhost:8000/workflow-version-execution-results?workflow_version_execution_id=717&return_all_outputs=true
19 | response:
20 | body:
21 | string: '{"Node 1":{"status":"SUCCESS","value":"AAA","error_message":null,"raw_error_message":null,"is_output_node":true}}
22 |
23 | '
24 | headers:
25 | Connection:
26 | - close
27 | Content-Length:
28 | - '114'
29 | Content-Type:
30 | - application/json
31 | Date:
32 | - Fri, 11 Apr 2025 17:44:48 GMT
33 | Server:
34 | - gunicorn
35 | status:
36 | code: 200
37 | message: OK
38 | version: 1
39 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_make_message_listener_2.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: ''
4 | headers:
5 | accept:
6 | - '*/*'
7 | accept-encoding:
8 | - gzip, deflate
9 | connection:
10 | - keep-alive
11 | host:
12 | - localhost:8000
13 | user-agent:
14 | - python-httpx/0.28.1
15 | x-api-key:
16 | - sanitized
17 | method: GET
18 | uri: http://localhost:8000/workflow-version-execution-results?workflow_version_execution_id=717&return_all_outputs=false
19 | response:
20 | body:
21 | string: '"AAA"
22 |
23 | '
24 | headers:
25 | Connection:
26 | - close
27 | Content-Length:
28 | - '6'
29 | Content-Type:
30 | - application/json
31 | Date:
32 | - Fri, 11 Apr 2025 17:50:28 GMT
33 | Server:
34 | - gunicorn
35 | status:
36 | code: 200
37 | message: OK
38 | version: 1
39 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_openai_chat_completion.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"messages": [{"role": "system", "content": "You are a helpful assistant."},
4 | {"role": "user", "content": "What is the capital of the United States?"}], "model":
5 | "gpt-3.5-turbo"}'
6 | headers:
7 | Authorization:
8 | - sanitized
9 | accept:
10 | - application/json
11 | accept-encoding:
12 | - gzip, deflate
13 | connection:
14 | - keep-alive
15 | content-length:
16 | - '167'
17 | content-type:
18 | - application/json
19 | host:
20 | - api.openai.com
21 | user-agent:
22 | - OpenAI/Python 1.60.1
23 | x-stainless-arch:
24 | - x64
25 | x-stainless-async:
26 | - 'false'
27 | x-stainless-lang:
28 | - python
29 | x-stainless-os:
30 | - Linux
31 | x-stainless-package-version:
32 | - 1.60.1
33 | x-stainless-retry-count:
34 | - '0'
35 | x-stainless-runtime:
36 | - CPython
37 | x-stainless-runtime-version:
38 | - 3.9.21
39 | method: POST
40 | uri: https://api.openai.com/v1/chat/completions
41 | response:
42 | body:
43 | string: "{\n \"id\": \"chatcmpl-BMF5JYzBZVfT2ZlGYxqkF28JzXY40\",\n \"object\":
44 | \"chat.completion\",\n \"created\": 1744640901,\n \"model\": \"gpt-3.5-turbo-0125\",\n
45 | \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
46 | \"assistant\",\n \"content\": \"The capital of the United States is
47 | Washington, D.C.\",\n \"refusal\": null,\n \"annotations\":
48 | []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n
49 | \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 26,\n \"completion_tokens\":
50 | 13,\n \"total_tokens\": 39,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
51 | 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
52 | {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
53 | 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
54 | \"default\",\n \"system_fingerprint\": null\n}\n"
55 | headers:
56 | CF-RAY:
57 | - 9303e3a389b7b2ae-WAW
58 | Connection:
59 | - keep-alive
60 | Content-Type:
61 | - application/json
62 | Date:
63 | - Mon, 14 Apr 2025 14:28:22 GMT
64 | Server:
65 | - cloudflare
66 | Set-Cookie:
67 | - __cf_bm=asAq5.fAzn4GN3d3Clf7SMYh5fKPdxOfRtkg19TS.9s-1744640902-1.0.1.1-6GeWxM_pNOewi6QQm3ZMnQpdGNSsbKSpsCLUsOWp5SSzQFdqAO6AS2uDvRfde4.0faNtH5S88bAhahhTEA7rtCZ4TUXC0dKKy3Q2Uzbz5s8;
68 | path=/; expires=Mon, 14-Apr-25 14:58:22 GMT; domain=.api.openai.com; HttpOnly;
69 | Secure; SameSite=None
70 | - _cfuvid=ssFq5AKYbNGAntlXKXazI26GSxiccoFDtBHzwfl2eSk-1744640902280-0.0.1.1-604800000;
71 | path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
72 | Transfer-Encoding:
73 | - chunked
74 | X-Content-Type-Options:
75 | - nosniff
76 | access-control-expose-headers:
77 | - X-Request-ID
78 | alt-svc:
79 | - h3=":443"; ma=86400
80 | cf-cache-status:
81 | - DYNAMIC
82 | content-length:
83 | - '844'
84 | openai-organization:
85 | - promptlayer-qcpdch
86 | openai-processing-ms:
87 | - '378'
88 | openai-version:
89 | - '2020-10-01'
90 | strict-transport-security:
91 | - max-age=31536000; includeSubDomains; preload
92 | x-ratelimit-limit-requests:
93 | - '10000'
94 | x-ratelimit-limit-tokens:
95 | - '50000000'
96 | x-ratelimit-remaining-requests:
97 | - '9999'
98 | x-ratelimit-remaining-tokens:
99 | - '49999979'
100 | x-ratelimit-reset-requests:
101 | - 6ms
102 | x-ratelimit-reset-tokens:
103 | - 0s
104 | x-request-id:
105 | - req_06c8c965862225f458b19e76bf93ddad
106 | status:
107 | code: 200
108 | message: OK
109 | - request:
110 | body: '{"function_name": "openai.OpenAI.chat.completions.create", "provider_type":
111 | "openai", "args": [], "kwargs": {"model": "gpt-3.5-turbo", "messages": [{"role":
112 | "system", "content": "You are a helpful assistant."}, {"role": "user", "content":
113 | "What is the capital of the United States?"}]}, "tags": null, "request_response":
114 | {"id": "chatcmpl-BMF5JYzBZVfT2ZlGYxqkF28JzXY40", "choices": [{"finish_reason":
115 | "stop", "index": 0, "logprobs": null, "message": {"content": "The capital of
116 | the United States is Washington, D.C.", "refusal": null, "role": "assistant",
117 | "audio": null, "function_call": null, "tool_calls": null, "annotations": []}}],
118 | "created": 1744640901, "model": "gpt-3.5-turbo-0125", "object": "chat.completion",
119 | "service_tier": "default", "system_fingerprint": null, "usage": {"completion_tokens":
120 | 13, "prompt_tokens": 26, "total_tokens": 39, "completion_tokens_details": {"accepted_prediction_tokens":
121 | 0, "audio_tokens": 0, "reasoning_tokens": 0, "rejected_prediction_tokens": 0},
122 | "prompt_tokens_details": {"audio_tokens": 0, "cached_tokens": 0}}}, "request_start_time":
123 | 1744640901.32603, "request_end_time": 1744640902.440252, "metadata": null, "span_id":
124 | null, "api_key": "sanitized"}'
125 | headers:
126 | Accept:
127 | - '*/*'
128 | Accept-Encoding:
129 | - gzip, deflate
130 | Connection:
131 | - keep-alive
132 | Content-Length:
133 | - '1219'
134 | Content-Type:
135 | - application/json
136 | User-Agent:
137 | - python-requests/2.31.0
138 | method: POST
139 | uri: http://localhost:8000/track-request
140 | response:
141 | body:
142 | string: '{"success":true,"request_id":142,"prompt_blueprint":null,"message":"Request
143 | tracked successfully"}
144 |
145 | '
146 | headers:
147 | Connection:
148 | - close
149 | Content-Length:
150 | - '99'
151 | Content-Type:
152 | - application/json
153 | Date:
154 | - Mon, 14 Apr 2025 14:28:23 GMT
155 | Server:
156 | - gunicorn
157 | status:
158 | code: 200
159 | message: OK
160 | version: 1
161 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_openai_chat_completion_async.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"messages": [{"role": "system", "content": "You are a helpful assistant."},
4 | {"role": "user", "content": "What is the capital of Spain?"}], "model": "gpt-3.5-turbo"}'
5 | headers:
6 | Authorization:
7 | - sanitized
8 | accept:
9 | - application/json
10 | accept-encoding:
11 | - gzip, deflate
12 | connection:
13 | - keep-alive
14 | content-length:
15 | - '155'
16 | content-type:
17 | - application/json
18 | host:
19 | - api.openai.com
20 | user-agent:
21 | - AsyncOpenAI/Python 1.60.1
22 | x-stainless-arch:
23 | - x64
24 | x-stainless-async:
25 | - async:asyncio
26 | x-stainless-lang:
27 | - python
28 | x-stainless-os:
29 | - Linux
30 | x-stainless-package-version:
31 | - 1.60.1
32 | x-stainless-retry-count:
33 | - '0'
34 | x-stainless-runtime:
35 | - CPython
36 | x-stainless-runtime-version:
37 | - 3.9.21
38 | method: POST
39 | uri: https://api.openai.com/v1/chat/completions
40 | response:
41 | body:
42 | string: "{\n \"id\": \"chatcmpl-BMF5PSareXOsQYsC6m0ZnUiZe9zRF\",\n \"object\":
43 | \"chat.completion\",\n \"created\": 1744640907,\n \"model\": \"gpt-3.5-turbo-0125\",\n
44 | \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
45 | \"assistant\",\n \"content\": \"The capital of Spain is Madrid.\",\n
46 | \ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\":
47 | null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
48 | 24,\n \"completion_tokens\": 8,\n \"total_tokens\": 32,\n \"prompt_tokens_details\":
49 | {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
50 | {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
51 | 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
52 | \"default\",\n \"system_fingerprint\": null\n}\n"
53 | headers:
54 | CF-RAY:
55 | - 9303e3c84eaf028d-WAW
56 | Connection:
57 | - keep-alive
58 | Content-Type:
59 | - application/json
60 | Date:
61 | - Mon, 14 Apr 2025 14:28:28 GMT
62 | Server:
63 | - cloudflare
64 | Set-Cookie:
65 | - __cf_bm=yGQsBej6ZdooDKD3nbgyNAc7HEQhihhK43OTMSdmmUw-1744640908-1.0.1.1-arrO5aZwzfXaxJ0G7s6zelVqazVdgZArOdj67sqyAGAv2ksu.9g46pt.mjl37VojQV8bx0mkAYirbBsKKyvDAhRBmy2yQXK7C5AMLSv.skE;
66 | path=/; expires=Mon, 14-Apr-25 14:58:28 GMT; domain=.api.openai.com; HttpOnly;
67 | Secure; SameSite=None
68 | - _cfuvid=o9lJW29lcd4Vmp0bHZ190SPyYzO4etEkW68VLJZynO8-1744640908222-0.0.1.1-604800000;
69 | path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
70 | Transfer-Encoding:
71 | - chunked
72 | X-Content-Type-Options:
73 | - nosniff
74 | access-control-expose-headers:
75 | - X-Request-ID
76 | alt-svc:
77 | - h3=":443"; ma=86400
78 | cf-cache-status:
79 | - DYNAMIC
80 | content-length:
81 | - '822'
82 | openai-organization:
83 | - promptlayer-qcpdch
84 | openai-processing-ms:
85 | - '490'
86 | openai-version:
87 | - '2020-10-01'
88 | strict-transport-security:
89 | - max-age=31536000; includeSubDomains; preload
90 | x-ratelimit-limit-requests:
91 | - '10000'
92 | x-ratelimit-limit-tokens:
93 | - '50000000'
94 | x-ratelimit-remaining-requests:
95 | - '9999'
96 | x-ratelimit-remaining-tokens:
97 | - '49999982'
98 | x-ratelimit-reset-requests:
99 | - 6ms
100 | x-ratelimit-reset-tokens:
101 | - 0s
102 | x-request-id:
103 | - req_49da8b49147700184eec1d3835fff43f
104 | status:
105 | code: 200
106 | message: OK
107 | - request:
108 | body: '{"function_name": "openai.AsyncOpenAI.chat.completions.create", "provider_type":
109 | "openai", "args": [], "kwargs": {"model": "gpt-3.5-turbo", "messages": [{"role":
110 | "system", "content": "You are a helpful assistant."}, {"role": "user", "content":
111 | "What is the capital of Spain?"}]}, "tags": null, "request_response": {"id":
112 | "chatcmpl-BMF5PSareXOsQYsC6m0ZnUiZe9zRF", "choices": [{"finish_reason": "stop",
113 | "index": 0, "logprobs": null, "message": {"content": "The capital of Spain is
114 | Madrid.", "refusal": null, "role": "assistant", "audio": null, "function_call":
115 | null, "tool_calls": null, "annotations": []}}], "created": 1744640907, "model":
116 | "gpt-3.5-turbo-0125", "object": "chat.completion", "service_tier": "default",
117 | "system_fingerprint": null, "usage": {"completion_tokens": 8, "prompt_tokens":
118 | 24, "total_tokens": 32, "completion_tokens_details": {"accepted_prediction_tokens":
119 | 0, "audio_tokens": 0, "reasoning_tokens": 0, "rejected_prediction_tokens": 0},
120 | "prompt_tokens_details": {"audio_tokens": 0, "cached_tokens": 0}}}, "request_start_time":
121 | 1744640907.141194, "request_end_time": 1744640908.310717, "metadata": null,
122 | "span_id": null, "api_key": "sanitized"}'
123 | headers:
124 | Accept:
125 | - '*/*'
126 | Accept-Encoding:
127 | - gzip, deflate
128 | Connection:
129 | - keep-alive
130 | Content-Length:
131 | - '1191'
132 | Content-Type:
133 | - application/json
134 | User-Agent:
135 | - python-requests/2.31.0
136 | method: POST
137 | uri: http://localhost:8000/track-request
138 | response:
139 | body:
140 | string: '{"success":true,"request_id":146,"prompt_blueprint":null,"message":"Request
141 | tracked successfully"}
142 |
143 | '
144 | headers:
145 | Connection:
146 | - close
147 | Content-Length:
148 | - '99'
149 | Content-Type:
150 | - application/json
151 | Date:
152 | - Mon, 14 Apr 2025 14:28:28 GMT
153 | Server:
154 | - gunicorn
155 | status:
156 | code: 200
157 | message: OK
158 | version: 1
159 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_openai_chat_completion_async_stream_with_pl_id.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"messages": [{"role": "system", "content": "You are a helpful assistant."},
4 | {"role": "user", "content": "What is the capital of Japan?"}], "model": "gpt-3.5-turbo",
5 | "stream": true}'
6 | headers:
7 | Authorization:
8 | - sanitized
9 | accept:
10 | - application/json
11 | accept-encoding:
12 | - gzip, deflate
13 | connection:
14 | - keep-alive
15 | content-length:
16 | - '169'
17 | content-type:
18 | - application/json
19 | host:
20 | - api.openai.com
21 | user-agent:
22 | - AsyncOpenAI/Python 1.60.1
23 | x-stainless-arch:
24 | - x64
25 | x-stainless-async:
26 | - async:asyncio
27 | x-stainless-lang:
28 | - python
29 | x-stainless-os:
30 | - Linux
31 | x-stainless-package-version:
32 | - 1.60.1
33 | x-stainless-retry-count:
34 | - '0'
35 | x-stainless-runtime:
36 | - CPython
37 | x-stainless-runtime-version:
38 | - 3.9.21
39 | method: POST
40 | uri: https://api.openai.com/v1/chat/completions
41 | response:
42 | body:
43 | string: 'data: {"id":"chatcmpl-BMdbS6FFVXc0L0Lff4PNpdD4bBiHS","object":"chat.completion.chunk","created":1744735150,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
44 |
45 |
46 | data: {"id":"chatcmpl-BMdbS6FFVXc0L0Lff4PNpdD4bBiHS","object":"chat.completion.chunk","created":1744735150,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null}]}
47 |
48 |
49 | data: {"id":"chatcmpl-BMdbS6FFVXc0L0Lff4PNpdD4bBiHS","object":"chat.completion.chunk","created":1744735150,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
50 | capital"},"logprobs":null,"finish_reason":null}]}
51 |
52 |
53 | data: {"id":"chatcmpl-BMdbS6FFVXc0L0Lff4PNpdD4bBiHS","object":"chat.completion.chunk","created":1744735150,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
54 | of"},"logprobs":null,"finish_reason":null}]}
55 |
56 |
57 | data: {"id":"chatcmpl-BMdbS6FFVXc0L0Lff4PNpdD4bBiHS","object":"chat.completion.chunk","created":1744735150,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
58 | Japan"},"logprobs":null,"finish_reason":null}]}
59 |
60 |
61 | data: {"id":"chatcmpl-BMdbS6FFVXc0L0Lff4PNpdD4bBiHS","object":"chat.completion.chunk","created":1744735150,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
62 | is"},"logprobs":null,"finish_reason":null}]}
63 |
64 |
65 | data: {"id":"chatcmpl-BMdbS6FFVXc0L0Lff4PNpdD4bBiHS","object":"chat.completion.chunk","created":1744735150,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
66 | Tokyo"},"logprobs":null,"finish_reason":null}]}
67 |
68 |
69 | data: {"id":"chatcmpl-BMdbS6FFVXc0L0Lff4PNpdD4bBiHS","object":"chat.completion.chunk","created":1744735150,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]}
70 |
71 |
72 | data: {"id":"chatcmpl-BMdbS6FFVXc0L0Lff4PNpdD4bBiHS","object":"chat.completion.chunk","created":1744735150,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
73 |
74 |
75 | data: [DONE]
76 |
77 |
78 | '
79 | headers:
80 | CF-RAY:
81 | - 930ce0a26ad0bfdc-WAW
82 | Connection:
83 | - keep-alive
84 | Content-Type:
85 | - text/event-stream; charset=utf-8
86 | Date:
87 | - Tue, 15 Apr 2025 16:39:10 GMT
88 | Server:
89 | - cloudflare
90 | Set-Cookie:
91 | - __cf_bm=owHlo75MxP3eFr.9TfCGxn7wO87C61Ng3NpOFCTHSag-1744735150-1.0.1.1-1GZYNGbsUwqT.moq0QiTPuJNJRDY47FyALqZQOLJq8bUjaZaOunV3w41kj32UUiTWxY4g5xjnYf2UtrpiIfMY2hLbtTs1TqqSSRHFFU9K3c;
92 | path=/; expires=Tue, 15-Apr-25 17:09:10 GMT; domain=.api.openai.com; HttpOnly;
93 | Secure; SameSite=None
94 | - _cfuvid=u1rLvIyukh.jiMK0dFiJV8fXDI_9tldUTgg3Rtd870U-1744735150802-0.0.1.1-604800000;
95 | path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
96 | Transfer-Encoding:
97 | - chunked
98 | X-Content-Type-Options:
99 | - nosniff
100 | access-control-expose-headers:
101 | - X-Request-ID
102 | alt-svc:
103 | - h3=":443"; ma=86400
104 | cf-cache-status:
105 | - DYNAMIC
106 | openai-organization:
107 | - promptlayer-qcpdch
108 | openai-processing-ms:
109 | - '157'
110 | openai-version:
111 | - '2020-10-01'
112 | strict-transport-security:
113 | - max-age=31536000; includeSubDomains; preload
114 | x-ratelimit-limit-requests:
115 | - '10000'
116 | x-ratelimit-limit-tokens:
117 | - '50000000'
118 | x-ratelimit-remaining-requests:
119 | - '9999'
120 | x-ratelimit-remaining-tokens:
121 | - '49999982'
122 | x-ratelimit-reset-requests:
123 | - 6ms
124 | x-ratelimit-reset-tokens:
125 | - 0s
126 | x-request-id:
127 | - req_4894347b8de2d2c55d62b68f3115d720
128 | status:
129 | code: 200
130 | message: OK
131 | - request:
132 | body: '{"function_name": "openai.AsyncOpenAI.chat.completions.create", "provider_type":
133 | "openai", "args": [], "kwargs": {"model": "gpt-3.5-turbo", "messages": [{"role":
134 | "system", "content": "You are a helpful assistant."}, {"role": "user", "content":
135 | "What is the capital of Japan?"}], "stream": true}, "tags": null, "request_response":
136 | {"id": "chatcmpl-BMdbS6FFVXc0L0Lff4PNpdD4bBiHS", "choices": [{"role": "assistant",
137 | "content": "The capital of Japan is Tokyo."}], "created": 1744735150, "model":
138 | "gpt-3.5-turbo-0125", "object": "chat.completion.chunk", "service_tier": "default",
139 | "system_fingerprint": null, "usage": null}, "request_start_time": 1744735149.915745,
140 | "request_end_time": 1744735150.952056, "metadata": null, "span_id": null, "api_key":
141 | "sanitized"}'
142 | headers:
143 | Accept:
144 | - '*/*'
145 | Accept-Encoding:
146 | - gzip, deflate
147 | Connection:
148 | - keep-alive
149 | Content-Length:
150 | - '784'
151 | Content-Type:
152 | - application/json
153 | User-Agent:
154 | - python-requests/2.31.0
155 | method: POST
156 | uri: http://localhost:8000/track-request
157 | response:
158 | body:
159 | string: '{"success":true,"request_id":194,"prompt_blueprint":null,"message":"Request
160 | tracked successfully"}
161 |
162 | '
163 | headers:
164 | Connection:
165 | - close
166 | Content-Length:
167 | - '99'
168 | Content-Type:
169 | - application/json
170 | Date:
171 | - Tue, 15 Apr 2025 16:39:10 GMT
172 | Server:
173 | - gunicorn
174 | status:
175 | code: 200
176 | message: OK
177 | version: 1
178 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_openai_chat_completion_with_pl_id.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"messages": [{"role": "system", "content": "You are a helpful assistant."},
4 | {"role": "user", "content": "What is the capital of France?"}], "model": "gpt-3.5-turbo"}'
5 | headers:
6 | Authorization:
7 | - sanitized
8 | accept:
9 | - application/json
10 | accept-encoding:
11 | - gzip, deflate
12 | connection:
13 | - keep-alive
14 | content-length:
15 | - '156'
16 | content-type:
17 | - application/json
18 | host:
19 | - api.openai.com
20 | user-agent:
21 | - OpenAI/Python 1.60.1
22 | x-stainless-arch:
23 | - x64
24 | x-stainless-async:
25 | - 'false'
26 | x-stainless-lang:
27 | - python
28 | x-stainless-os:
29 | - Linux
30 | x-stainless-package-version:
31 | - 1.60.1
32 | x-stainless-retry-count:
33 | - '0'
34 | x-stainless-runtime:
35 | - CPython
36 | x-stainless-runtime-version:
37 | - 3.9.21
38 | method: POST
39 | uri: https://api.openai.com/v1/chat/completions
40 | response:
41 | body:
42 | string: "{\n \"id\": \"chatcmpl-BMF5LwsMO72CHnWgcbAnjfEmQBNrD\",\n \"object\":
43 | \"chat.completion\",\n \"created\": 1744640903,\n \"model\": \"gpt-3.5-turbo-0125\",\n
44 | \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
45 | \"assistant\",\n \"content\": \"The capital of France is Paris.\",\n
46 | \ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\":
47 | null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
48 | 24,\n \"completion_tokens\": 8,\n \"total_tokens\": 32,\n \"prompt_tokens_details\":
49 | {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
50 | {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
51 | 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
52 | \"default\",\n \"system_fingerprint\": null\n}\n"
53 | headers:
54 | CF-RAY:
55 | - 9303e3af6cd8c400-WAW
56 | Connection:
57 | - keep-alive
58 | Content-Type:
59 | - application/json
60 | Date:
61 | - Mon, 14 Apr 2025 14:28:23 GMT
62 | Server:
63 | - cloudflare
64 | Set-Cookie:
65 | - __cf_bm=3wdOjuuM.rHpXdeK0QVNlO.JyHD39oAGuAxMasU_8rc-1744640903-1.0.1.1-NvlYqk5oPAdrFZJu1tWMvL4rOSpdI6WJPSWsurF81AfhH7_.ziZoBDXdqdkQOwWfUgiIg3N0LPq8fWmJReEi1UbFFf_yj8m_o.T.XP7SRUw;
66 | path=/; expires=Mon, 14-Apr-25 14:58:23 GMT; domain=.api.openai.com; HttpOnly;
67 | Secure; SameSite=None
68 | - _cfuvid=3H5YNE.WZQDluf1GCd9NICuuD7ok1n__xYmXczeaUr0-1744640903981-0.0.1.1-604800000;
69 | path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
70 | Transfer-Encoding:
71 | - chunked
72 | X-Content-Type-Options:
73 | - nosniff
74 | access-control-expose-headers:
75 | - X-Request-ID
76 | alt-svc:
77 | - h3=":443"; ma=86400
78 | cf-cache-status:
79 | - DYNAMIC
80 | content-length:
81 | - '822'
82 | openai-organization:
83 | - promptlayer-qcpdch
84 | openai-processing-ms:
85 | - '198'
86 | openai-version:
87 | - '2020-10-01'
88 | strict-transport-security:
89 | - max-age=31536000; includeSubDomains; preload
90 | x-ratelimit-limit-requests:
91 | - '10000'
92 | x-ratelimit-limit-tokens:
93 | - '50000000'
94 | x-ratelimit-remaining-requests:
95 | - '9999'
96 | x-ratelimit-remaining-tokens:
97 | - '49999981'
98 | x-ratelimit-reset-requests:
99 | - 6ms
100 | x-ratelimit-reset-tokens:
101 | - 0s
102 | x-request-id:
103 | - req_75c76b62b338f2dcbef3efa1fed3ad8f
104 | status:
105 | code: 200
106 | message: OK
107 | - request:
108 | body: '{"function_name": "openai.OpenAI.chat.completions.create", "provider_type":
109 | "openai", "args": [], "kwargs": {"model": "gpt-3.5-turbo", "messages": [{"role":
110 | "system", "content": "You are a helpful assistant."}, {"role": "user", "content":
111 | "What is the capital of France?"}]}, "tags": null, "request_response": {"id":
112 | "chatcmpl-BMF5LwsMO72CHnWgcbAnjfEmQBNrD", "choices": [{"finish_reason": "stop",
113 | "index": 0, "logprobs": null, "message": {"content": "The capital of France
114 | is Paris.", "refusal": null, "role": "assistant", "audio": null, "function_call":
115 | null, "tool_calls": null, "annotations": []}}], "created": 1744640903, "model":
116 | "gpt-3.5-turbo-0125", "object": "chat.completion", "service_tier": "default",
117 | "system_fingerprint": null, "usage": {"completion_tokens": 8, "prompt_tokens":
118 | 24, "total_tokens": 32, "completion_tokens_details": {"accepted_prediction_tokens":
119 | 0, "audio_tokens": 0, "reasoning_tokens": 0, "rejected_prediction_tokens": 0},
120 | "prompt_tokens_details": {"audio_tokens": 0, "cached_tokens": 0}}}, "request_start_time":
121 | 1744640903.246829, "request_end_time": 1744640904.11391, "metadata": null, "span_id":
122 | null, "api_key": "sanitized"}'
123 | headers:
124 | Accept:
125 | - '*/*'
126 | Accept-Encoding:
127 | - gzip, deflate
128 | Connection:
129 | - keep-alive
130 | Content-Length:
131 | - '1186'
132 | Content-Type:
133 | - application/json
134 | User-Agent:
135 | - python-requests/2.31.0
136 | method: POST
137 | uri: http://localhost:8000/track-request
138 | response:
139 | body:
140 | string: '{"success":true,"request_id":143,"prompt_blueprint":null,"message":"Request
141 | tracked successfully"}
142 |
143 | '
144 | headers:
145 | Connection:
146 | - close
147 | Content-Length:
148 | - '99'
149 | Content-Type:
150 | - application/json
151 | Date:
152 | - Mon, 14 Apr 2025 14:28:24 GMT
153 | Server:
154 | - gunicorn
155 | status:
156 | code: 200
157 | message: OK
158 | version: 1
159 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_openai_chat_completion_with_stream.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"messages": [{"role": "system", "content": "You are a helpful assistant."},
4 | {"role": "user", "content": "What is the capital of Germany?"}], "model": "gpt-3.5-turbo",
5 | "stream": true}'
6 | headers:
7 | Authorization:
8 | - sanitized
9 | accept:
10 | - application/json
11 | accept-encoding:
12 | - gzip, deflate
13 | connection:
14 | - keep-alive
15 | content-length:
16 | - '171'
17 | content-type:
18 | - application/json
19 | host:
20 | - api.openai.com
21 | user-agent:
22 | - OpenAI/Python 1.60.1
23 | x-stainless-arch:
24 | - x64
25 | x-stainless-async:
26 | - 'false'
27 | x-stainless-lang:
28 | - python
29 | x-stainless-os:
30 | - Linux
31 | x-stainless-package-version:
32 | - 1.60.1
33 | x-stainless-retry-count:
34 | - '0'
35 | x-stainless-runtime:
36 | - CPython
37 | x-stainless-runtime-version:
38 | - 3.9.21
39 | method: POST
40 | uri: https://api.openai.com/v1/chat/completions
41 | response:
42 | body:
43 | string: 'data: {"id":"chatcmpl-BMF5NbakpXjwzGqWVrdgpWmPl3maf","object":"chat.completion.chunk","created":1744640905,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
44 |
45 |
46 | data: {"id":"chatcmpl-BMF5NbakpXjwzGqWVrdgpWmPl3maf","object":"chat.completion.chunk","created":1744640905,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null}]}
47 |
48 |
49 | data: {"id":"chatcmpl-BMF5NbakpXjwzGqWVrdgpWmPl3maf","object":"chat.completion.chunk","created":1744640905,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
50 | capital"},"logprobs":null,"finish_reason":null}]}
51 |
52 |
53 | data: {"id":"chatcmpl-BMF5NbakpXjwzGqWVrdgpWmPl3maf","object":"chat.completion.chunk","created":1744640905,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
54 | of"},"logprobs":null,"finish_reason":null}]}
55 |
56 |
57 | data: {"id":"chatcmpl-BMF5NbakpXjwzGqWVrdgpWmPl3maf","object":"chat.completion.chunk","created":1744640905,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
58 | Germany"},"logprobs":null,"finish_reason":null}]}
59 |
60 |
61 | data: {"id":"chatcmpl-BMF5NbakpXjwzGqWVrdgpWmPl3maf","object":"chat.completion.chunk","created":1744640905,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
62 | is"},"logprobs":null,"finish_reason":null}]}
63 |
64 |
65 | data: {"id":"chatcmpl-BMF5NbakpXjwzGqWVrdgpWmPl3maf","object":"chat.completion.chunk","created":1744640905,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
66 | Berlin"},"logprobs":null,"finish_reason":null}]}
67 |
68 |
69 | data: {"id":"chatcmpl-BMF5NbakpXjwzGqWVrdgpWmPl3maf","object":"chat.completion.chunk","created":1744640905,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]}
70 |
71 |
72 | data: {"id":"chatcmpl-BMF5NbakpXjwzGqWVrdgpWmPl3maf","object":"chat.completion.chunk","created":1744640905,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
73 |
74 |
75 | data: [DONE]
76 |
77 |
78 | '
79 | headers:
80 | CF-RAY:
81 | - 9303e3b97d9aee43-WAW
82 | Connection:
83 | - keep-alive
84 | Content-Type:
85 | - text/event-stream; charset=utf-8
86 | Date:
87 | - Mon, 14 Apr 2025 14:28:25 GMT
88 | Server:
89 | - cloudflare
90 | Set-Cookie:
91 | - __cf_bm=xMMqpDvXgNP3YoKXMISg5xOd1C3mTCpGqaiUMwsha6E-1744640905-1.0.1.1-LzdGOmaDopBp1uyC0uWC8N0Sj3zAX1fkvTd1HCGycCm2CcVQqVY.Rb.4TPW0XtqyfWQVb5Nu0SvVyWqTfMvN50tYEEApS_TDy6cK.fp2fBA;
92 | path=/; expires=Mon, 14-Apr-25 14:58:25 GMT; domain=.api.openai.com; HttpOnly;
93 | Secure; SameSite=None
94 | - _cfuvid=uYNVMf0snr9XwFkTi4vdpPBn6P4TsPZ4NvLIFmWzLxo-1744640905801-0.0.1.1-604800000;
95 | path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
96 | Transfer-Encoding:
97 | - chunked
98 | X-Content-Type-Options:
99 | - nosniff
100 | access-control-expose-headers:
101 | - X-Request-ID
102 | alt-svc:
103 | - h3=":443"; ma=86400
104 | cf-cache-status:
105 | - DYNAMIC
106 | openai-organization:
107 | - promptlayer-qcpdch
108 | openai-processing-ms:
109 | - '166'
110 | openai-version:
111 | - '2020-10-01'
112 | strict-transport-security:
113 | - max-age=31536000; includeSubDomains; preload
114 | x-ratelimit-limit-requests:
115 | - '10000'
116 | x-ratelimit-limit-tokens:
117 | - '50000000'
118 | x-ratelimit-remaining-requests:
119 | - '9999'
120 | x-ratelimit-remaining-tokens:
121 | - '49999982'
122 | x-ratelimit-reset-requests:
123 | - 6ms
124 | x-ratelimit-reset-tokens:
125 | - 0s
126 | x-request-id:
127 | - req_2dcac69f7e0b12ae13eeaf140154d64a
128 | status:
129 | code: 200
130 | message: OK
131 | version: 1
132 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_openai_chat_completion_with_stream_and_pl_id.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"messages": [{"role": "system", "content": "You are a helpful assistant."},
4 | {"role": "user", "content": "What is the capital of Italy?"}], "model": "gpt-3.5-turbo",
5 | "stream": true}'
6 | headers:
7 | Authorization:
8 | - sanitized
9 | accept:
10 | - application/json
11 | accept-encoding:
12 | - gzip, deflate
13 | connection:
14 | - keep-alive
15 | content-length:
16 | - '169'
17 | content-type:
18 | - application/json
19 | host:
20 | - api.openai.com
21 | user-agent:
22 | - OpenAI/Python 1.60.1
23 | x-stainless-arch:
24 | - x64
25 | x-stainless-async:
26 | - 'false'
27 | x-stainless-lang:
28 | - python
29 | x-stainless-os:
30 | - Linux
31 | x-stainless-package-version:
32 | - 1.60.1
33 | x-stainless-retry-count:
34 | - '0'
35 | x-stainless-runtime:
36 | - CPython
37 | x-stainless-runtime-version:
38 | - 3.9.21
39 | method: POST
40 | uri: https://api.openai.com/v1/chat/completions
41 | response:
42 | body:
43 | string: 'data: {"id":"chatcmpl-BMdbRwh3IezC3YV86Kga7wqbM1eTR","object":"chat.completion.chunk","created":1744735149,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
44 |
45 |
46 | data: {"id":"chatcmpl-BMdbRwh3IezC3YV86Kga7wqbM1eTR","object":"chat.completion.chunk","created":1744735149,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null}]}
47 |
48 |
49 | data: {"id":"chatcmpl-BMdbRwh3IezC3YV86Kga7wqbM1eTR","object":"chat.completion.chunk","created":1744735149,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
50 | capital"},"logprobs":null,"finish_reason":null}]}
51 |
52 |
53 | data: {"id":"chatcmpl-BMdbRwh3IezC3YV86Kga7wqbM1eTR","object":"chat.completion.chunk","created":1744735149,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
54 | of"},"logprobs":null,"finish_reason":null}]}
55 |
56 |
57 | data: {"id":"chatcmpl-BMdbRwh3IezC3YV86Kga7wqbM1eTR","object":"chat.completion.chunk","created":1744735149,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
58 | Italy"},"logprobs":null,"finish_reason":null}]}
59 |
60 |
61 | data: {"id":"chatcmpl-BMdbRwh3IezC3YV86Kga7wqbM1eTR","object":"chat.completion.chunk","created":1744735149,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
62 | is"},"logprobs":null,"finish_reason":null}]}
63 |
64 |
65 | data: {"id":"chatcmpl-BMdbRwh3IezC3YV86Kga7wqbM1eTR","object":"chat.completion.chunk","created":1744735149,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
66 | Rome"},"logprobs":null,"finish_reason":null}]}
67 |
68 |
69 | data: {"id":"chatcmpl-BMdbRwh3IezC3YV86Kga7wqbM1eTR","object":"chat.completion.chunk","created":1744735149,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]}
70 |
71 |
72 | data: {"id":"chatcmpl-BMdbRwh3IezC3YV86Kga7wqbM1eTR","object":"chat.completion.chunk","created":1744735149,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
73 |
74 |
75 | data: [DONE]
76 |
77 |
78 | '
79 | headers:
80 | CF-RAY:
81 | - 930ce09aae13bfbc-WAW
82 | Connection:
83 | - keep-alive
84 | Content-Type:
85 | - text/event-stream; charset=utf-8
86 | Date:
87 | - Tue, 15 Apr 2025 16:39:09 GMT
88 | Server:
89 | - cloudflare
90 | Set-Cookie:
91 | - __cf_bm=iGtr_.vGSmOCmTF9hbdxNTjlcDfcQehHfMag920d_aQ-1744735149-1.0.1.1-wCMVcusdGOgqys.g9R5OpprsTBuGJMzZGe_BKMH1Or.Mb_mf11xbysmuTSlkmgQA0cuOtNbGm_3ORj2agRByRKu.QBLhMBd4NMOUlKYK.B4;
92 | path=/; expires=Tue, 15-Apr-25 17:09:09 GMT; domain=.api.openai.com; HttpOnly;
93 | Secure; SameSite=None
94 | - _cfuvid=8PlnNLIPphAcygLXAO0anovDq9JtTy5Ct6To3J2Vbwo-1744735149686-0.0.1.1-604800000;
95 | path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
96 | Transfer-Encoding:
97 | - chunked
98 | X-Content-Type-Options:
99 | - nosniff
100 | access-control-expose-headers:
101 | - X-Request-ID
102 | alt-svc:
103 | - h3=":443"; ma=86400
104 | cf-cache-status:
105 | - DYNAMIC
106 | openai-organization:
107 | - promptlayer-qcpdch
108 | openai-processing-ms:
109 | - '157'
110 | openai-version:
111 | - '2020-10-01'
112 | strict-transport-security:
113 | - max-age=31536000; includeSubDomains; preload
114 | x-ratelimit-limit-requests:
115 | - '10000'
116 | x-ratelimit-limit-tokens:
117 | - '50000000'
118 | x-ratelimit-remaining-requests:
119 | - '9999'
120 | x-ratelimit-remaining-tokens:
121 | - '49999982'
122 | x-ratelimit-reset-requests:
123 | - 6ms
124 | x-ratelimit-reset-tokens:
125 | - 0s
126 | x-request-id:
127 | - req_1b7e6816013de365ec635a31eeb2da14
128 | status:
129 | code: 200
130 | message: OK
131 | - request:
132 | body: '{"function_name": "openai.OpenAI.chat.completions.create", "provider_type":
133 | "openai", "args": [], "kwargs": {"model": "gpt-3.5-turbo", "messages": [{"role":
134 | "system", "content": "You are a helpful assistant."}, {"role": "user", "content":
135 | "What is the capital of Italy?"}], "stream": true}, "tags": null, "request_response":
136 | {"id": "chatcmpl-BMdbRwh3IezC3YV86Kga7wqbM1eTR", "choices": [{"role": "assistant",
137 | "content": "The capital of Italy is Rome."}], "created": 1744735149, "model":
138 | "gpt-3.5-turbo-0125", "object": "chat.completion.chunk", "service_tier": "default",
139 | "system_fingerprint": null, "usage": null}, "request_start_time": 1744735148.737197,
140 | "request_end_time": 1744735149.818011, "metadata": null, "span_id": null, "api_key":
141 | "sanitized"}'
142 | headers:
143 | Accept:
144 | - '*/*'
145 | Accept-Encoding:
146 | - gzip, deflate
147 | Connection:
148 | - keep-alive
149 | Content-Length:
150 | - '778'
151 | Content-Type:
152 | - application/json
153 | User-Agent:
154 | - python-requests/2.31.0
155 | method: POST
156 | uri: http://localhost:8000/track-request
157 | response:
158 | body:
159 | string: '{"success":true,"request_id":193,"prompt_blueprint":null,"message":"Request
160 | tracked successfully"}
161 |
162 | '
163 | headers:
164 | Connection:
165 | - close
166 | Content-Length:
167 | - '99'
168 | Content-Type:
169 | - application/json
170 | Date:
171 | - Tue, 15 Apr 2025 16:39:09 GMT
172 | Server:
173 | - gunicorn
174 | status:
175 | code: 200
176 | message: OK
177 | version: 1
178 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_publish_template_async.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"prompt_template": {"prompt_name": "sample_template", "prompt_template":
4 | {"dataset_examples": [], "function_call": "none", "functions": [], "input_variables":
5 | [], "messages": [{"content": [{"text": "", "type": "text"}], "dataset_examples":
6 | [], "input_variables": [], "name": null, "raw_request_display_role": "", "role":
7 | "system", "template_format": "f-string"}, {"content": [{"text": "What is the
8 | capital of Japan?", "type": "text"}], "dataset_examples": [], "input_variables":
9 | [], "name": null, "raw_request_display_role": "", "role": "user", "template_format":
10 | "f-string"}], "tool_choice": null, "tools": null, "type": "chat"}, "tags": ["test"],
11 | "commit_message": "test", "metadata": {"model": {"name": "gpt-4o-mini", "provider":
12 | "openai", "parameters": {"frequency_penalty": 0, "max_tokens": 256, "messages":
13 | [{"content": "Hello", "role": "system"}], "model": "gpt-4o", "presence_penalty":
14 | 0, "seed": 0, "temperature": 1, "top_p": 1}}}}, "prompt_version": {"prompt_name":
15 | "sample_template", "prompt_template": {"dataset_examples": [], "function_call":
16 | "none", "functions": [], "input_variables": [], "messages": [{"content": [{"text":
17 | "", "type": "text"}], "dataset_examples": [], "input_variables": [], "name":
18 | null, "raw_request_display_role": "", "role": "system", "template_format": "f-string"},
19 | {"content": [{"text": "What is the capital of Japan?", "type": "text"}], "dataset_examples":
20 | [], "input_variables": [], "name": null, "raw_request_display_role": "", "role":
21 | "user", "template_format": "f-string"}], "tool_choice": null, "tools": null,
22 | "type": "chat"}, "tags": ["test"], "commit_message": "test", "metadata": {"model":
23 | {"name": "gpt-4o-mini", "provider": "openai", "parameters": {"frequency_penalty":
24 | 0, "max_tokens": 256, "messages": [{"content": "Hello", "role": "system"}],
25 | "model": "gpt-4o", "presence_penalty": 0, "seed": 0, "temperature": 1, "top_p":
26 | 1}}}}, "release_labels": null}'
27 | headers:
28 | Accept:
29 | - '*/*'
30 | Accept-Encoding:
31 | - gzip, deflate
32 | Connection:
33 | - keep-alive
34 | Content-Length:
35 | - '1907'
36 | Content-Type:
37 | - application/json
38 | User-Agent:
39 | - python-requests/2.31.0
40 | x-api-key:
41 | - sanitized
42 | method: POST
43 | uri: http://localhost:8000/rest/prompt-templates
44 | response:
45 | body:
46 | string: '{"id":4,"prompt_name":"sample_template","tags":["test"],"prompt_template":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":""}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"What
47 | is the capital of Japan?"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null}],"functions":[],"tools":null,"function_call":"none","tool_choice":null,"type":"chat","input_variables":[],"dataset_examples":[]},"commit_message":"test","metadata":{"model":{"provider":"openai","name":"gpt-4o-mini","parameters":{"frequency_penalty":0,"max_tokens":256,"messages":[{"content":"Hello","role":"system"}],"model":"gpt-4o","presence_penalty":0,"seed":0,"temperature":1,"top_p":1}}},"release_labels":null}
48 |
49 | '
50 | headers:
51 | Connection:
52 | - close
53 | Content-Length:
54 | - '870'
55 | Content-Type:
56 | - application/json
57 | Date:
58 | - Mon, 14 Apr 2025 08:48:13 GMT
59 | Server:
60 | - gunicorn
61 | status:
62 | code: 201
63 | message: CREATED
64 | version: 1
65 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_run_prompt_async.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"api_key": "sanitized"}'
4 | headers:
5 | accept:
6 | - '*/*'
7 | accept-encoding:
8 | - gzip, deflate
9 | connection:
10 | - keep-alive
11 | content-length:
12 | - '49'
13 | content-type:
14 | - application/json
15 | host:
16 | - localhost:8000
17 | user-agent:
18 | - python-httpx/0.28.1
19 | x-api-key:
20 | - sanitized
21 | method: POST
22 | uri: http://localhost:8000/prompt-templates/sample_template
23 | response:
24 | body:
25 | string: '{"id":4,"prompt_name":"sample_template","tags":["test"],"workspace_id":1,"commit_message":"test","metadata":{"model":{"provider":"openai","name":"gpt-4o-mini","parameters":{"frequency_penalty":0,"max_tokens":256,"messages":[{"content":"Hello","role":"system"}],"model":"gpt-4o","presence_penalty":0,"seed":0,"temperature":1,"top_p":1}}},"prompt_template":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":""}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"What
26 | is the capital of Japan?"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null}],"functions":[],"tools":null,"function_call":"none","tool_choice":null,"type":"chat","input_variables":[],"dataset_examples":[]},"llm_kwargs":{"messages":[{"content":"Hello","role":"system"}],"model":"gpt-4o","frequency_penalty":0,"max_tokens":256,"presence_penalty":0,"seed":0,"temperature":1,"top_p":1},"provider_base_url":null,"version":1,"snippets":[],"warning":null}
27 |
28 | '
29 | headers:
30 | Connection:
31 | - close
32 | Content-Length:
33 | - '1107'
34 | Content-Type:
35 | - application/json
36 | Date:
37 | - Mon, 14 Apr 2025 10:00:25 GMT
38 | Server:
39 | - gunicorn
40 | status:
41 | code: 200
42 | message: OK
43 | - request:
44 | body: '{"messages": [{"content": "Hello", "role": "system"}], "model": "gpt-4o",
45 | "frequency_penalty": 0, "max_tokens": 256, "presence_penalty": 0, "seed": 0,
46 | "stream": false, "temperature": 1, "top_p": 1}'
47 | headers:
48 | Authorization:
49 | - sanitized
50 | accept:
51 | - application/json
52 | accept-encoding:
53 | - gzip, deflate
54 | connection:
55 | - keep-alive
56 | content-length:
57 | - '177'
58 | content-type:
59 | - application/json
60 | host:
61 | - api.openai.com
62 | user-agent:
63 | - AsyncOpenAI/Python 1.60.1
64 | x-stainless-arch:
65 | - x64
66 | x-stainless-async:
67 | - async:asyncio
68 | x-stainless-lang:
69 | - python
70 | x-stainless-os:
71 | - Linux
72 | x-stainless-package-version:
73 | - 1.60.1
74 | x-stainless-retry-count:
75 | - '0'
76 | x-stainless-runtime:
77 | - CPython
78 | x-stainless-runtime-version:
79 | - 3.9.21
80 | method: POST
81 | uri: https://api.openai.com/v1/chat/completions
82 | response:
83 | body:
84 | string: "{\n \"id\": \"chatcmpl-BMAu3fBRyPYaswyFgBBnBQcB0YxUK\",\n \"object\":
85 | \"chat.completion\",\n \"created\": 1744624827,\n \"model\": \"gpt-4o-2024-08-06\",\n
86 | \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
87 | \"assistant\",\n \"content\": \"Hello! Yes, I'm trained on data up
88 | to October 2023. How can I assist you today?\",\n \"refusal\": null,\n
89 | \ \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\":
90 | \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 8,\n \"completion_tokens\":
91 | 23,\n \"total_tokens\": 31,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
92 | 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
93 | {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
94 | 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
95 | \"default\",\n \"system_fingerprint\": \"fp_92f14e8683\"\n}\n"
96 | headers:
97 | CF-RAY:
98 | - 93025b2f48841df9-WAW
99 | Connection:
100 | - keep-alive
101 | Content-Type:
102 | - application/json
103 | Date:
104 | - Mon, 14 Apr 2025 10:00:27 GMT
105 | Server:
106 | - cloudflare
107 | Set-Cookie:
108 | - __cf_bm=WoDMvktgogHkKC5dtVRbodUbJxV4Bc4sWuN0QjW1jm8-1744624827-1.0.1.1-9ZBIg_wk7E3fLa9s2.rAu96n.Ev9yOJCKpbuekBbDfGNAgqtPe9902bf5B61WTuP8z6zGS1vjqA8zsPJkBLnMwvq0JgYJ8Z9JTNzm_YjyJw;
109 | path=/; expires=Mon, 14-Apr-25 10:30:27 GMT; domain=.api.openai.com; HttpOnly;
110 | Secure; SameSite=None
111 | - _cfuvid=eHe50Lx9pdivakzkzleBOuxU1.KM94YO_a2dXQl8l_A-1744624827668-0.0.1.1-604800000;
112 | path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
113 | Transfer-Encoding:
114 | - chunked
115 | X-Content-Type-Options:
116 | - nosniff
117 | access-control-expose-headers:
118 | - X-Request-ID
119 | alt-svc:
120 | - h3=":443"; ma=86400
121 | cf-cache-status:
122 | - DYNAMIC
123 | content-length:
124 | - '880'
125 | openai-organization:
126 | - promptlayer-qcpdch
127 | openai-processing-ms:
128 | - '507'
129 | openai-version:
130 | - '2020-10-01'
131 | strict-transport-security:
132 | - max-age=31536000; includeSubDomains; preload
133 | x-ratelimit-limit-requests:
134 | - '50000'
135 | x-ratelimit-limit-tokens:
136 | - '150000000'
137 | x-ratelimit-remaining-requests:
138 | - '49999'
139 | x-ratelimit-remaining-tokens:
140 | - '149999995'
141 | x-ratelimit-reset-requests:
142 | - 1ms
143 | x-ratelimit-reset-tokens:
144 | - 0s
145 | x-request-id:
146 | - req_bd8a9e42286b527e321d668846c705a2
147 | status:
148 | code: 200
149 | message: OK
150 | - request:
151 | body: '{"function_name": "openai.chat.completions.create", "provider_type": "openai",
152 | "args": [], "kwargs": {"messages": [{"content": "Hello", "role": "system"}],
153 | "model": "gpt-4o", "frequency_penalty": 0, "max_tokens": 256, "presence_penalty":
154 | 0, "seed": 0, "temperature": 1, "top_p": 1, "stream": false}, "tags": null,
155 | "request_start_time": 1744624827.799539, "request_end_time": 1744624827.799546,
156 | "metadata": null, "prompt_id": 4, "prompt_version": 1, "prompt_input_variables":
157 | {}, "group_id": null, "return_prompt_blueprint": true, "span_id": null, "request_response":
158 | {"id": "chatcmpl-BMAu3fBRyPYaswyFgBBnBQcB0YxUK", "choices": [{"finish_reason":
159 | "stop", "index": 0, "logprobs": null, "message": {"content": "Hello! Yes, I''m
160 | trained on data up to October 2023. How can I assist you today?", "refusal":
161 | null, "role": "assistant", "audio": null, "function_call": null, "tool_calls":
162 | null, "annotations": []}}], "created": 1744624827, "model": "gpt-4o-2024-08-06",
163 | "object": "chat.completion", "service_tier": "default", "system_fingerprint":
164 | "fp_92f14e8683", "usage": {"completion_tokens": 23, "prompt_tokens": 8, "total_tokens":
165 | 31, "completion_tokens_details": {"accepted_prediction_tokens": 0, "audio_tokens":
166 | 0, "reasoning_tokens": 0, "rejected_prediction_tokens": 0}, "prompt_tokens_details":
167 | {"audio_tokens": 0, "cached_tokens": 0}}}, "api_key": "sanitized"}'
168 | headers:
169 | accept:
170 | - '*/*'
171 | accept-encoding:
172 | - gzip, deflate
173 | connection:
174 | - keep-alive
175 | content-length:
176 | - '1282'
177 | content-type:
178 | - application/json
179 | host:
180 | - localhost:8000
181 | user-agent:
182 | - python-httpx/0.28.1
183 | method: POST
184 | uri: http://localhost:8000/track-request
185 | response:
186 | body:
187 | string: '{"success":true,"request_id":129,"prompt_blueprint":{"prompt_template":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"Hello"}],"raw_request_display_role":"system","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"Hello!
188 | Yes, I''m trained on data up to October 2023. How can I assist you today?"}],"raw_request_display_role":"assistant","dataset_examples":[],"role":"assistant","function_call":null,"name":null,"tool_calls":null}],"functions":[],"tools":[],"function_call":null,"tool_choice":null,"type":"chat","input_variables":[],"dataset_examples":[]},"commit_message":null,"metadata":{"model":{"provider":"openai","name":"gpt-4o","parameters":{"frequency_penalty":0,"max_tokens":256,"presence_penalty":0,"seed":0,"temperature":1,"top_p":1,"stream":false}}},"provider_base_url_name":null,"report_id":null,"inference_client_name":null},"message":"Request
189 | tracked successfully"}
190 |
191 | '
192 | headers:
193 | Connection:
194 | - close
195 | Content-Length:
196 | - '1015'
197 | Content-Type:
198 | - application/json
199 | Date:
200 | - Mon, 14 Apr 2025 10:00:27 GMT
201 | Server:
202 | - gunicorn
203 | status:
204 | code: 200
205 | message: OK
206 | version: 1
207 |
--------------------------------------------------------------------------------
/tests/fixtures/cassettes/test_track_and_templates.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"provider": "openai", "model": "gpt-3.5-turbo", "api_key": "sanitized"}'
4 | headers:
5 | Accept:
6 | - '*/*'
7 | Accept-Encoding:
8 | - gzip, deflate
9 | Connection:
10 | - keep-alive
11 | Content-Length:
12 | - '98'
13 | Content-Type:
14 | - application/json
15 | User-Agent:
16 | - python-requests/2.31.0
17 | x-api-key:
18 | - sanitized
19 | method: POST
20 | uri: http://localhost:8000/prompt-templates/sample_template
21 | response:
22 | body:
23 | string: '{"id":4,"prompt_name":"sample_template","tags":["test"],"workspace_id":1,"commit_message":"test","metadata":{"model":{"provider":"openai","name":"gpt-4o-mini","parameters":{"frequency_penalty":0,"max_tokens":256,"messages":[{"content":"Hello","role":"system"}],"model":"gpt-4o","presence_penalty":0,"seed":0,"temperature":1,"top_p":1}}},"prompt_template":{"messages":[{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":""}],"raw_request_display_role":"","dataset_examples":[],"role":"system","name":null},{"input_variables":[],"template_format":"f-string","content":[{"type":"text","text":"What
24 | is the capital of Japan?"}],"raw_request_display_role":"","dataset_examples":[],"role":"user","name":null}],"functions":[],"tools":null,"function_call":"none","tool_choice":null,"type":"chat","input_variables":[],"dataset_examples":[]},"llm_kwargs":{"messages":[{"content":"Hello","role":"system"}],"model":"gpt-4o","frequency_penalty":0,"max_tokens":256,"presence_penalty":0,"seed":0,"temperature":1,"top_p":1},"provider_base_url":null,"version":1,"snippets":[],"warning":null}
25 |
26 | '
27 | headers:
28 | Connection:
29 | - close
30 | Content-Length:
31 | - '1107'
32 | Content-Type:
33 | - application/json
34 | Date:
35 | - Mon, 14 Apr 2025 10:48:24 GMT
36 | Server:
37 | - gunicorn
38 | status:
39 | code: 200
40 | message: OK
41 | - request:
42 | body: '{"messages": [{"content": "Hello", "role": "system"}], "model": "gpt-3.5-turbo",
43 | "frequency_penalty": 0, "max_tokens": 256, "presence_penalty": 0, "seed": 0,
44 | "temperature": 1, "top_p": 1}'
45 | headers:
46 | Authorization:
47 | - sanitized
48 | accept:
49 | - application/json
50 | accept-encoding:
51 | - gzip, deflate
52 | connection:
53 | - keep-alive
54 | content-length:
55 | - '169'
56 | content-type:
57 | - application/json
58 | host:
59 | - api.openai.com
60 | user-agent:
61 | - OpenAI/Python 1.60.1
62 | x-stainless-arch:
63 | - x64
64 | x-stainless-async:
65 | - 'false'
66 | x-stainless-lang:
67 | - python
68 | x-stainless-os:
69 | - Linux
70 | x-stainless-package-version:
71 | - 1.60.1
72 | x-stainless-retry-count:
73 | - '0'
74 | x-stainless-runtime:
75 | - CPython
76 | x-stainless-runtime-version:
77 | - 3.9.21
78 | method: POST
79 | uri: https://api.openai.com/v1/chat/completions
80 | response:
81 | body:
82 | string: "{\n \"id\": \"chatcmpl-BMBeSHzYtFfWOaJukmxcQxflVDr5S\",\n \"object\":
83 | \"chat.completion\",\n \"created\": 1744627704,\n \"model\": \"gpt-3.5-turbo-0125\",\n
84 | \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
85 | \"assistant\",\n \"content\": \"Hello! How can I assist you today?\",\n
86 | \ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\":
87 | null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
88 | 8,\n \"completion_tokens\": 10,\n \"total_tokens\": 18,\n \"prompt_tokens_details\":
89 | {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
90 | {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
91 | 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
92 | \"default\",\n \"system_fingerprint\": null\n}\n"
93 | headers:
94 | CF-RAY:
95 | - 9302a1731995b243-WAW
96 | Connection:
97 | - keep-alive
98 | Content-Type:
99 | - application/json
100 | Date:
101 | - Mon, 14 Apr 2025 10:48:25 GMT
102 | Server:
103 | - cloudflare
104 | Set-Cookie:
105 | - __cf_bm=dkxNTdwzyxu8C_XBrYGCBAIev6t5Rb9LOD0RScWu3Hw-1744627705-1.0.1.1-2P3mi4WpCJJUE82kaE7nJw1gaDRRSj.wiDv9ZhaeWkxJerxuvAb.AWlvlLTJg41n3_56LUg_bKyWkJzRvMBL93JV1q.IZQ8Fg3Heo.DAHas;
106 | path=/; expires=Mon, 14-Apr-25 11:18:25 GMT; domain=.api.openai.com; HttpOnly;
107 | Secure; SameSite=None
108 | - _cfuvid=r5S0oTEM7vtkEpEEeOj.TuhQ5P0mhmRWZY87g0PtRXw-1744627705219-0.0.1.1-604800000;
109 | path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
110 | Transfer-Encoding:
111 | - chunked
112 | X-Content-Type-Options:
113 | - nosniff
114 | access-control-expose-headers:
115 | - X-Request-ID
116 | alt-svc:
117 | - h3=":443"; ma=86400
118 | cf-cache-status:
119 | - DYNAMIC
120 | content-length:
121 | - '825'
122 | openai-organization:
123 | - promptlayer-qcpdch
124 | openai-processing-ms:
125 | - '229'
126 | openai-version:
127 | - '2020-10-01'
128 | strict-transport-security:
129 | - max-age=31536000; includeSubDomains; preload
130 | x-ratelimit-limit-requests:
131 | - '10000'
132 | x-ratelimit-limit-tokens:
133 | - '50000000'
134 | x-ratelimit-remaining-requests:
135 | - '9999'
136 | x-ratelimit-remaining-tokens:
137 | - '49999995'
138 | x-ratelimit-reset-requests:
139 | - 6ms
140 | x-ratelimit-reset-tokens:
141 | - 0s
142 | x-request-id:
143 | - req_86424c52e8d7f558b64fce8ed9457451
144 | status:
145 | code: 200
146 | message: OK
147 | - request:
148 | body: '{"function_name": "openai.OpenAI.chat.completions.create", "provider_type":
149 | "openai", "args": [], "kwargs": {"model": "gpt-3.5-turbo", "messages": [{"content":
150 | "Hello", "role": "system"}], "frequency_penalty": 0, "max_tokens": 256, "presence_penalty":
151 | 0, "seed": 0, "temperature": 1, "top_p": 1}, "tags": null, "request_response":
152 | {"id": "chatcmpl-BMBeSHzYtFfWOaJukmxcQxflVDr5S", "choices": [{"finish_reason":
153 | "stop", "index": 0, "logprobs": null, "message": {"content": "Hello! How can
154 | I assist you today?", "refusal": null, "role": "assistant", "audio": null, "function_call":
155 | null, "tool_calls": null, "annotations": []}}], "created": 1744627704, "model":
156 | "gpt-3.5-turbo-0125", "object": "chat.completion", "service_tier": "default",
157 | "system_fingerprint": null, "usage": {"completion_tokens": 10, "prompt_tokens":
158 | 8, "total_tokens": 18, "completion_tokens_details": {"accepted_prediction_tokens":
159 | 0, "audio_tokens": 0, "reasoning_tokens": 0, "rejected_prediction_tokens": 0},
160 | "prompt_tokens_details": {"audio_tokens": 0, "cached_tokens": 0}}}, "request_start_time":
161 | 1744627704.311881, "request_end_time": 1744627705.33816, "metadata": null, "span_id":
162 | null, "api_key": "sanitized"}'
163 | headers:
164 | Accept:
165 | - '*/*'
166 | Accept-Encoding:
167 | - gzip, deflate
168 | Connection:
169 | - keep-alive
170 | Content-Length:
171 | - '1210'
172 | Content-Type:
173 | - application/json
174 | User-Agent:
175 | - python-requests/2.31.0
176 | method: POST
177 | uri: http://localhost:8000/track-request
178 | response:
179 | body:
180 | string: '{"success":true,"request_id":132,"prompt_blueprint":null,"message":"Request
181 | tracked successfully"}
182 |
183 | '
184 | headers:
185 | Connection:
186 | - close
187 | Content-Length:
188 | - '99'
189 | Content-Type:
190 | - application/json
191 | Date:
192 | - Mon, 14 Apr 2025 10:48:25 GMT
193 | Server:
194 | - gunicorn
195 | status:
196 | code: 200
197 | message: OK
198 | - request:
199 | body: '{"request_id": 132, "score": 10, "name": "accuracy", "api_key": "sanitized"}'
200 | headers:
201 | Accept:
202 | - '*/*'
203 | Accept-Encoding:
204 | - gzip, deflate
205 | Connection:
206 | - keep-alive
207 | Content-Length:
208 | - '102'
209 | Content-Type:
210 | - application/json
211 | User-Agent:
212 | - python-requests/2.31.0
213 | method: POST
214 | uri: http://localhost:8000/library-track-score
215 | response:
216 | body:
217 | string: '{"success":true}
218 |
219 | '
220 | headers:
221 | Connection:
222 | - close
223 | Content-Length:
224 | - '17'
225 | Content-Type:
226 | - application/json
227 | Date:
228 | - Mon, 14 Apr 2025 10:48:25 GMT
229 | Server:
230 | - gunicorn
231 | status:
232 | code: 200
233 | message: OK
234 | - request:
235 | body: '{"request_id": 132, "metadata": {"test": "test"}, "api_key": "sanitized"}'
236 | headers:
237 | Accept:
238 | - '*/*'
239 | Accept-Encoding:
240 | - gzip, deflate
241 | Connection:
242 | - keep-alive
243 | Content-Length:
244 | - '99'
245 | Content-Type:
246 | - application/json
247 | User-Agent:
248 | - python-requests/2.31.0
249 | method: POST
250 | uri: http://localhost:8000/library-track-metadata
251 | response:
252 | body:
253 | string: '{"success":true}
254 |
255 | '
256 | headers:
257 | Connection:
258 | - close
259 | Content-Length:
260 | - '17'
261 | Content-Type:
262 | - application/json
263 | Date:
264 | - Mon, 14 Apr 2025 10:48:25 GMT
265 | Server:
266 | - gunicorn
267 | status:
268 | code: 200
269 | message: OK
270 | - request:
271 | body: '{"api_key": "sanitized"}'
272 | headers:
273 | Accept:
274 | - '*/*'
275 | Accept-Encoding:
276 | - gzip, deflate
277 | Connection:
278 | - keep-alive
279 | Content-Length:
280 | - '50'
281 | Content-Type:
282 | - application/json
283 | User-Agent:
284 | - python-requests/2.31.0
285 | method: POST
286 | uri: http://localhost:8000/create-group
287 | response:
288 | body:
289 | string: '{"success":true,"id":1}
290 |
291 | '
292 | headers:
293 | Connection:
294 | - close
295 | Content-Length:
296 | - '24'
297 | Content-Type:
298 | - application/json
299 | Date:
300 | - Mon, 14 Apr 2025 10:48:25 GMT
301 | Server:
302 | - gunicorn
303 | status:
304 | code: 200
305 | message: OK
306 | - request:
307 | body: '{"request_id": 132, "group_id": 1, "api_key": "sanitized"}'
308 | headers:
309 | Accept:
310 | - '*/*'
311 | Accept-Encoding:
312 | - gzip, deflate
313 | Connection:
314 | - keep-alive
315 | Content-Length:
316 | - '84'
317 | Content-Type:
318 | - application/json
319 | User-Agent:
320 | - python-requests/2.31.0
321 | method: POST
322 | uri: http://localhost:8000/track-group
323 | response:
324 | body:
325 | string: '{"success":true}
326 |
327 | '
328 | headers:
329 | Connection:
330 | - close
331 | Content-Length:
332 | - '17'
333 | Content-Type:
334 | - application/json
335 | Date:
336 | - Mon, 14 Apr 2025 10:48:25 GMT
337 | Server:
338 | - gunicorn
339 | status:
340 | code: 200
341 | message: OK
342 | version: 1
343 |
--------------------------------------------------------------------------------
/tests/fixtures/clients.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from promptlayer import AsyncPromptLayer, PromptLayer
4 |
5 |
6 | @pytest.fixture
7 | def promptlayer_client(promptlayer_api_key):
8 | return PromptLayer(api_key=promptlayer_api_key)
9 |
10 |
11 | @pytest.fixture
12 | def promptlayer_async_client(promptlayer_api_key):
13 | return AsyncPromptLayer(api_key=promptlayer_api_key)
14 |
15 |
16 | @pytest.fixture
17 | def anthropic_client(promptlayer_client, anthropic_api_key):
18 | return promptlayer_client.anthropic.Anthropic(api_key=anthropic_api_key)
19 |
20 |
21 | @pytest.fixture
22 | def anthropic_async_client(promptlayer_client, anthropic_api_key):
23 | return promptlayer_client.anthropic.AsyncAnthropic(api_key=anthropic_api_key)
24 |
25 |
26 | @pytest.fixture
27 | def openai_client(promptlayer_client, openai_api_key):
28 | return promptlayer_client.openai.OpenAI(api_key=openai_api_key)
29 |
30 |
31 | @pytest.fixture
32 | def openai_async_client(promptlayer_client, openai_api_key):
33 | return promptlayer_client.openai.AsyncOpenAI(api_key=openai_api_key)
34 |
--------------------------------------------------------------------------------
/tests/fixtures/setup.py:
--------------------------------------------------------------------------------
1 | from unittest.mock import patch
2 |
3 | import pytest
4 |
5 | from tests.utils.vcr import is_cassette_recording
6 |
7 | if is_cassette_recording():
8 |
9 | @pytest.fixture
10 | def autouse_disable_network():
11 | return
12 | else:
13 |
14 | @pytest.fixture(autouse=True)
15 | def autouse_disable_network(disable_network):
16 | yield
17 |
18 |
19 | @pytest.fixture(scope="session", autouse=True)
20 | def setup():
21 | with patch("promptlayer.utils.URL_API_PROMPTLAYER", "http://localhost:8000"):
22 | yield
23 |
--------------------------------------------------------------------------------
/tests/fixtures/templates.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.fixture
5 | def sample_template_name():
6 | return "sample_template"
7 |
8 |
9 | @pytest.fixture
10 | def sample_template_content():
11 | return {
12 | "dataset_examples": [],
13 | "function_call": "none",
14 | "functions": [],
15 | "input_variables": [],
16 | "messages": [
17 | {
18 | "content": [{"text": "", "type": "text"}],
19 | "dataset_examples": [],
20 | "input_variables": [],
21 | "name": None,
22 | "raw_request_display_role": "",
23 | "role": "system",
24 | "template_format": "f-string",
25 | },
26 | {
27 | "content": [{"text": "What is the capital of Japan?", "type": "text"}],
28 | "dataset_examples": [],
29 | "input_variables": [],
30 | "name": None,
31 | "raw_request_display_role": "",
32 | "role": "user",
33 | "template_format": "f-string",
34 | },
35 | ],
36 | "tool_choice": None,
37 | "tools": None,
38 | "type": "chat",
39 | }
40 |
--------------------------------------------------------------------------------
/tests/fixtures/workflow_update_messages.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.fixture
5 | def workflow_update_data_no_result_code():
6 | return {
7 | "final_output": {
8 | "Node 1": {
9 | "status": "SUCCESS",
10 | "value": "no_result_code",
11 | "error_message": None,
12 | "raw_error_message": None,
13 | "is_output_node": None,
14 | }
15 | },
16 | "workflow_version_execution_id": 717,
17 | }
18 |
19 |
20 | @pytest.fixture
21 | def workflow_update_data_ok():
22 | return {
23 | "final_output": {
24 | "Node 1": {
25 | "status": "SUCCESS",
26 | "value": "ok_result_code",
27 | "error_message": None,
28 | "raw_error_message": None,
29 | "is_output_node": None,
30 | }
31 | },
32 | "result_code": "OK",
33 | "workflow_version_execution_id": 717,
34 | }
35 |
36 |
37 | @pytest.fixture
38 | def workflow_update_data_exceeds_size_limit():
39 | return {
40 | "final_output": (
41 | "Final output (and associated metadata) exceeds the size limit of 1 bytes. "
42 | "Upgrade to the most recent SDK or use GET /workflow-version-execution-results "
43 | "to retrieve the final output."
44 | ),
45 | "result_code": "EXCEEDS_SIZE_LIMIT",
46 | "workflow_version_execution_id": 717,
47 | }
48 |
--------------------------------------------------------------------------------
/tests/test_agents/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MagnivOrg/prompt-layer-library/2ac9bc5d0a37d90e108d53647a602c84b7f9b58a/tests/test_agents/__init__.py
--------------------------------------------------------------------------------
/tests/test_agents/test_arun_workflow_request.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from contextlib import nullcontext
3 | from unittest.mock import AsyncMock, MagicMock, patch
4 |
5 | import pytest
6 | from ably.realtime.realtime_channel import RealtimeChannel
7 | from pytest_parametrize_cases import Case, parametrize_cases
8 |
9 | from promptlayer.utils import arun_workflow_request
10 | from tests.utils.mocks import Any
11 | from tests.utils.vcr import assert_played, is_cassette_recording
12 |
13 |
14 | @patch("promptlayer.utils.URL_API_PROMPTLAYER", "http://localhost:8000")
15 | @patch("promptlayer.utils.WS_TOKEN_REQUEST_LIBRARY_URL", "http://localhost:8000/ws-token-request-library")
16 | @parametrize_cases(
17 | Case("Regular call", kwargs={"workflow_id_or_name": "analyze_1", "input_variables": {"var1": "value1"}}),
18 | Case("Legacy call", kwargs={"workflow_name": "analyze_1", "input_variables": {"var1": "value1"}}),
19 | )
20 | @pytest.mark.asyncio
21 | async def test_arun_workflow_request(promptlayer_api_key, kwargs):
22 | is_recording = is_cassette_recording()
23 | results_future = MagicMock()
24 | message_listener = MagicMock()
25 | with (
26 | assert_played("test_arun_workflow_request.yaml") as cassette,
27 | patch(
28 | "promptlayer.utils._make_channel_name_suffix", return_value="8dd7e4d404754c60a50e78f70f74aade"
29 | ) as _make_channel_name_suffix_mock,
30 | nullcontext()
31 | if is_recording
32 | else patch(
33 | "promptlayer.utils._subscribe_to_workflow_completion_channel",
34 | return_value=(results_future, message_listener),
35 | ) as _subscribe_to_workflow_completion_channel_mock,
36 | nullcontext()
37 | if is_recording
38 | else patch(
39 | "promptlayer.utils._wait_for_workflow_completion",
40 | new_callable=AsyncMock,
41 | return_value={"Node 2": "False", "Node 3": "AAA"},
42 | ) as _wait_for_workflow_completion_mock,
43 | ):
44 | assert await arun_workflow_request(api_key=promptlayer_api_key, **kwargs) == {
45 | "Node 2": "False",
46 | "Node 3": "AAA",
47 | }
48 | assert [(request.method, request.uri) for request in cassette.requests] == [
49 | ("GET", "http://localhost:8000/workflows/analyze_1"),
50 | (
51 | "POST",
52 | (
53 | "http://localhost:8000/ws-token-request-library?"
54 | "capability=workflows%3A3%3Arun%3A8dd7e4d404754c60a50e78f70f74aade"
55 | ),
56 | ),
57 | ("POST", "http://localhost:8000/workflows/3/run"),
58 | ]
59 |
60 | _make_channel_name_suffix_mock.assert_called_once()
61 | if not is_recording:
62 | _subscribe_to_workflow_completion_channel_mock.assert_awaited_once_with(
63 | Any(type_=RealtimeChannel), Any(type_=asyncio.Future), False, {"X-API-KEY": promptlayer_api_key}
64 | )
65 | _wait_for_workflow_completion_mock.assert_awaited_once_with(
66 | Any(type_=RealtimeChannel), results_future, message_listener, 3600
67 | )
68 |
--------------------------------------------------------------------------------
/tests/test_agents/test_misc.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import json
3 | from unittest.mock import AsyncMock, MagicMock
4 |
5 | import pytest
6 | from ably.types.message import Message
7 |
8 | from promptlayer.utils import _get_final_output, _make_message_listener, _wait_for_workflow_completion
9 | from tests.utils.vcr import assert_played
10 |
11 |
12 | @pytest.mark.asyncio
13 | async def test_get_final_output(headers):
14 | with assert_played("test_get_final_output_1.yaml"):
15 | assert (await _get_final_output(717, True, headers=headers)) == {
16 | "Node 1": {
17 | "status": "SUCCESS",
18 | "value": "AAA",
19 | "error_message": None,
20 | "raw_error_message": None,
21 | "is_output_node": True,
22 | }
23 | }
24 |
25 | with assert_played("test_get_final_output_2.yaml"):
26 | assert (await _get_final_output(717, False, headers=headers)) == "AAA"
27 |
28 |
29 | @pytest.mark.asyncio
30 | async def test_make_message_listener(
31 | headers, workflow_update_data_no_result_code, workflow_update_data_ok, workflow_update_data_exceeds_size_limit
32 | ):
33 | results_future = asyncio.Future()
34 | execution_id_future = asyncio.Future()
35 | message_listener = _make_message_listener(results_future, execution_id_future, True, headers)
36 | execution_id_future.set_result(717)
37 | await message_listener(Message(name="INVALID"))
38 | assert not results_future.done()
39 | assert execution_id_future.done()
40 |
41 | # Final output is in the message
42 | for message_data in (workflow_update_data_no_result_code, workflow_update_data_ok):
43 | results_future = asyncio.Future()
44 | execution_id_future = asyncio.Future()
45 | execution_id_future.set_result(717)
46 | message_listener = _make_message_listener(results_future, execution_id_future, True, headers)
47 | await message_listener(Message(name="SET_WORKFLOW_COMPLETE", data=json.dumps(message_data)))
48 | assert results_future.done()
49 | assert (await asyncio.wait_for(results_future, 0.1)) == message_data["final_output"]
50 |
51 | # Final output is not in the message (return all outputs)
52 | with assert_played("test_make_message_listener_1.yaml"):
53 | results_future = asyncio.Future()
54 | execution_id_future = asyncio.Future()
55 | execution_id_future.set_result(717)
56 | message_listener = _make_message_listener(results_future, execution_id_future, True, headers)
57 | await message_listener(
58 | Message(name="SET_WORKFLOW_COMPLETE", data=json.dumps(workflow_update_data_exceeds_size_limit))
59 | )
60 | assert results_future.done()
61 | assert (await asyncio.wait_for(results_future, 0.1)) == {
62 | "Node 1": {
63 | "status": "SUCCESS",
64 | "value": "AAA",
65 | "error_message": None,
66 | "raw_error_message": None,
67 | "is_output_node": True,
68 | }
69 | }
70 |
71 | # Final output is not in the message (return final output)
72 | with assert_played("test_make_message_listener_2.yaml"):
73 | results_future = asyncio.Future()
74 | execution_id_future = asyncio.Future()
75 | execution_id_future.set_result(717)
76 | message_listener = _make_message_listener(results_future, execution_id_future, False, headers)
77 | await message_listener(
78 | Message(name="SET_WORKFLOW_COMPLETE", data=json.dumps(workflow_update_data_exceeds_size_limit))
79 | )
80 | assert results_future.done()
81 | assert (await asyncio.wait_for(results_future, 0.1)) == "AAA"
82 |
83 |
84 | @pytest.mark.asyncio
85 | async def test_wait_for_workflow_completion(workflow_update_data_ok):
86 | mock_channel = AsyncMock()
87 | mock_channel.unsubscribe = MagicMock()
88 | results_future = asyncio.Future()
89 | results_future.set_result(workflow_update_data_ok["final_output"])
90 | message_listener = AsyncMock()
91 | actual_result = await _wait_for_workflow_completion(mock_channel, results_future, message_listener, 120)
92 | assert workflow_update_data_ok["final_output"] == actual_result
93 | mock_channel.unsubscribe.assert_called_once_with("SET_WORKFLOW_COMPLETE", message_listener)
94 |
--------------------------------------------------------------------------------
/tests/test_anthropic_proxy.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from anthropic.types import (
3 | Message,
4 | MessageDeltaUsage,
5 | RawContentBlockDeltaEvent,
6 | RawContentBlockStartEvent,
7 | RawContentBlockStopEvent,
8 | RawMessageDeltaEvent,
9 | RawMessageStartEvent,
10 | RawMessageStopEvent,
11 | TextBlock,
12 | TextDelta,
13 | )
14 | from anthropic.types.raw_message_delta_event import Delta
15 | from anthropic.types.usage import Usage
16 |
17 | from tests.utils.vcr import assert_played
18 |
19 |
20 | def test_anthropic_chat_completion(capsys, anthropic_client):
21 | with assert_played("test_anthropic_chat_completion.yaml"):
22 | completion = anthropic_client.messages.create(
23 | max_tokens=1024,
24 | messages=[{"role": "user", "content": "What is the capital of the United States?"}],
25 | model="claude-3-haiku-20240307",
26 | )
27 |
28 | captured = capsys.readouterr()
29 | assert "WARNING: While" not in captured.err
30 | assert completion.content is not None
31 |
32 |
33 | def test_anthropic_chat_completion_with_pl_id(capsys, anthropic_client):
34 | with assert_played("test_anthropic_chat_completion_with_pl_id.yaml"):
35 | completion, pl_id = anthropic_client.messages.create(
36 | max_tokens=1024,
37 | messages=[{"role": "user", "content": "What is the capital of France?"}],
38 | model="claude-3-haiku-20240307",
39 | return_pl_id=True,
40 | )
41 |
42 | assert "WARNING: While" not in capsys.readouterr().err
43 | assert completion.content is not None
44 | assert isinstance(pl_id, int)
45 |
46 |
47 | def test_anthropic_chat_completion_with_stream(capsys, anthropic_client):
48 | with assert_played("test_anthropic_chat_completion_with_stream.yaml"):
49 | completions_gen = anthropic_client.messages.create(
50 | max_tokens=1024,
51 | messages=[{"role": "user", "content": "What is the capital of Germany?"}],
52 | model="claude-3-haiku-20240307",
53 | stream=True,
54 | )
55 |
56 | completions = [completion for completion in completions_gen]
57 | assert completions == [
58 | RawMessageStartEvent(
59 | message=Message(
60 | id="msg_01PP15qoPAWehXLzXosCnnVP",
61 | content=[],
62 | model="claude-3-haiku-20240307",
63 | role="assistant",
64 | stop_reason=None,
65 | stop_sequence=None,
66 | type="message",
67 | usage=Usage(
68 | cache_creation_input_tokens=0, cache_read_input_tokens=0, input_tokens=14, output_tokens=4
69 | ),
70 | ),
71 | type="message_start",
72 | ),
73 | RawContentBlockStartEvent(
74 | content_block=TextBlock(citations=None, text="", type="text"), index=0, type="content_block_start"
75 | ),
76 | RawContentBlockDeltaEvent(
77 | delta=TextDelta(text="The capital of Germany", type="text_delta"), index=0, type="content_block_delta"
78 | ),
79 | RawContentBlockDeltaEvent(
80 | delta=TextDelta(text=" is Berlin.", type="text_delta"), index=0, type="content_block_delta"
81 | ),
82 | RawContentBlockStopEvent(index=0, type="content_block_stop"),
83 | RawMessageDeltaEvent(
84 | delta=Delta(stop_reason="end_turn", stop_sequence=None),
85 | type="message_delta",
86 | usage=MessageDeltaUsage(output_tokens=10),
87 | ),
88 | RawMessageStopEvent(type="message_stop"),
89 | ]
90 |
91 | assert "WARNING: While" not in capsys.readouterr().err
92 |
93 |
94 | def test_anthropic_chat_completion_with_stream_and_pl_id(anthropic_client):
95 | with assert_played("test_anthropic_chat_completion_with_stream_and_pl_id.yaml"):
96 | completions_gen = anthropic_client.messages.create(
97 | max_tokens=1024,
98 | messages=[{"role": "user", "content": "What is the capital of Italy?"}],
99 | model="claude-3-haiku-20240307",
100 | stream=True,
101 | return_pl_id=True,
102 | )
103 | completions = [completion for completion, _ in completions_gen]
104 | assert completions == [
105 | RawMessageStartEvent(
106 | message=Message(
107 | id="msg_016q2kSZ82qtDP2CNUAKSfLV",
108 | content=[],
109 | model="claude-3-haiku-20240307",
110 | role="assistant",
111 | stop_reason=None,
112 | stop_sequence=None,
113 | type="message",
114 | usage=Usage(
115 | cache_creation_input_tokens=0, cache_read_input_tokens=0, input_tokens=14, output_tokens=4
116 | ),
117 | ),
118 | type="message_start",
119 | ),
120 | RawContentBlockStartEvent(
121 | content_block=TextBlock(citations=None, text="", type="text"), index=0, type="content_block_start"
122 | ),
123 | RawContentBlockDeltaEvent(
124 | delta=TextDelta(text="The capital of Italy", type="text_delta"), index=0, type="content_block_delta"
125 | ),
126 | RawContentBlockDeltaEvent(
127 | delta=TextDelta(text=" is Rome.", type="text_delta"), index=0, type="content_block_delta"
128 | ),
129 | RawContentBlockStopEvent(index=0, type="content_block_stop"),
130 | RawMessageDeltaEvent(
131 | delta=Delta(stop_reason="end_turn", stop_sequence=None),
132 | type="message_delta",
133 | usage=MessageDeltaUsage(output_tokens=10),
134 | ),
135 | RawMessageStopEvent(type="message_stop"),
136 | ]
137 |
138 |
139 | @pytest.mark.asyncio
140 | async def test_anthropic_chat_completion_async(capsys, anthropic_async_client):
141 | with assert_played("test_anthropic_chat_completion_async.yaml"):
142 | completion = await anthropic_async_client.messages.create(
143 | max_tokens=1024,
144 | messages=[{"role": "user", "content": "What is the capital of Spain?"}],
145 | model="claude-3-haiku-20240307",
146 | )
147 |
148 | captured = capsys.readouterr()
149 | assert "WARNING: While" not in captured.err
150 | assert completion.content is not None
151 |
152 |
153 | @pytest.mark.asyncio
154 | async def test_anthropic_chat_completion_async_stream_with_pl_id(anthropic_async_client):
155 | with assert_played("test_anthropic_chat_completion_async_stream_with_pl_id.yaml"):
156 | completions_gen = await anthropic_async_client.messages.create(
157 | max_tokens=1024,
158 | messages=[{"role": "user", "content": "What is the capital of Japan?"}],
159 | model="claude-3-haiku-20240307",
160 | stream=True,
161 | return_pl_id=True,
162 | )
163 |
164 | completions = [completion async for completion, _ in completions_gen]
165 | assert completions == [
166 | RawMessageStartEvent(
167 | message=Message(
168 | id="msg_01Bi6S5crUgtL7PUCuYc8Vy6",
169 | content=[],
170 | model="claude-3-haiku-20240307",
171 | role="assistant",
172 | stop_reason=None,
173 | stop_sequence=None,
174 | type="message",
175 | usage=Usage(
176 | cache_creation_input_tokens=0, cache_read_input_tokens=0, input_tokens=14, output_tokens=4
177 | ),
178 | ),
179 | type="message_start",
180 | ),
181 | RawContentBlockStartEvent(
182 | content_block=TextBlock(citations=None, text="", type="text"), index=0, type="content_block_start"
183 | ),
184 | RawContentBlockDeltaEvent(
185 | delta=TextDelta(text="The capital of Japan", type="text_delta"), index=0, type="content_block_delta"
186 | ),
187 | RawContentBlockDeltaEvent(
188 | delta=TextDelta(text=" is Tokyo.", type="text_delta"), index=0, type="content_block_delta"
189 | ),
190 | RawContentBlockStopEvent(index=0, type="content_block_stop"),
191 | RawMessageDeltaEvent(
192 | delta=Delta(stop_reason="end_turn", stop_sequence=None),
193 | type="message_delta",
194 | usage=MessageDeltaUsage(output_tokens=10),
195 | ),
196 | RawMessageStopEvent(type="message_stop"),
197 | ]
198 |
--------------------------------------------------------------------------------
/tests/test_get_prompt_template.py:
--------------------------------------------------------------------------------
1 | from tests.utils.vcr import assert_played
2 |
3 |
4 | def test_get_prompt_template_provider_base_url_name(capsys, promptlayer_client):
5 | # TODO(dmu) HIGH: Improve assertions for this test
6 | provider_base_url_name = "does_not_exist"
7 | prompt_template = {
8 | "type": "chat",
9 | "provider_base_url_name": provider_base_url_name,
10 | "messages": [
11 | {
12 | "content": [{"text": "You are an AI.", "type": "text"}],
13 | "input_variables": [],
14 | "name": None,
15 | "raw_request_display_role": "",
16 | "role": "system",
17 | "template_format": "f-string",
18 | },
19 | {
20 | "content": [{"text": "What is the capital of Japan?", "type": "text"}],
21 | "input_variables": [],
22 | "name": None,
23 | "raw_request_display_role": "",
24 | "role": "user",
25 | "template_format": "f-string",
26 | },
27 | ],
28 | }
29 |
30 | prompt_registry_name = "test_template:test"
31 | with assert_played("test_get_prompt_template_provider_base_url_name.yaml"):
32 | promptlayer_client.templates.publish(
33 | {
34 | "provider_base_url_name": provider_base_url_name,
35 | "prompt_name": prompt_registry_name,
36 | "prompt_template": prompt_template,
37 | }
38 | )
39 | response = promptlayer_client.templates.get(
40 | prompt_registry_name, {"provider": "openai", "model": "gpt-3.5-turbo"}
41 | )
42 | assert response["provider_base_url"] is None
43 |
--------------------------------------------------------------------------------
/tests/test_openai_proxy.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from tests.utils.vcr import assert_played
4 |
5 |
6 | def test_openai_chat_completion(capsys, openai_client):
7 | with assert_played("test_openai_chat_completion.yaml"):
8 | completion = openai_client.chat.completions.create(
9 | model="gpt-3.5-turbo",
10 | messages=[
11 | {"role": "system", "content": "You are a helpful assistant."},
12 | {"role": "user", "content": "What is the capital of the United States?"},
13 | ],
14 | )
15 |
16 | captured = capsys.readouterr()
17 | assert "WARNING: While" not in captured.err
18 | assert completion.choices[0].message.content is not None
19 |
20 |
21 | def test_openai_chat_completion_with_pl_id(capsys, openai_client):
22 | with assert_played("test_openai_chat_completion_with_pl_id.yaml"):
23 | completion, pl_id = openai_client.chat.completions.create(
24 | model="gpt-3.5-turbo",
25 | messages=[
26 | {"role": "system", "content": "You are a helpful assistant."},
27 | {"role": "user", "content": "What is the capital of France?"},
28 | ],
29 | return_pl_id=True,
30 | )
31 |
32 | captured = capsys.readouterr()
33 | assert "WARNING: While" not in captured.err
34 | assert completion.choices[0].message.content is not None
35 | assert isinstance(pl_id, int)
36 |
37 |
38 | def test_openai_chat_completion_with_stream(capsys, openai_client):
39 | with assert_played("test_openai_chat_completion_with_stream.yaml"):
40 | completion = openai_client.chat.completions.create(
41 | model="gpt-3.5-turbo",
42 | messages=[
43 | {"role": "system", "content": "You are a helpful assistant."},
44 | {"role": "user", "content": "What is the capital of Germany?"},
45 | ],
46 | stream=True,
47 | )
48 |
49 | captured = capsys.readouterr()
50 | for chunk in completion:
51 | assert chunk.choices[0].delta != {}
52 | assert "WARNING: While" not in captured.err
53 |
54 |
55 | def test_openai_chat_completion_with_stream_and_pl_id(capsys, openai_client):
56 | with assert_played("test_openai_chat_completion_with_stream_and_pl_id.yaml"):
57 | completion = openai_client.chat.completions.create(
58 | model="gpt-3.5-turbo",
59 | messages=[
60 | {"role": "system", "content": "You are a helpful assistant."},
61 | {"role": "user", "content": "What is the capital of Italy?"},
62 | ],
63 | stream=True,
64 | return_pl_id=True,
65 | )
66 |
67 | pl_id = None
68 | for _, pl_id in completion:
69 | assert pl_id is None or isinstance(pl_id, int)
70 | assert isinstance(pl_id, int)
71 |
72 | assert "WARNING: While" not in capsys.readouterr().err
73 |
74 |
75 | @pytest.mark.asyncio
76 | async def test_openai_chat_completion_async(capsys, openai_async_client):
77 | with assert_played("test_openai_chat_completion_async.yaml"):
78 | completion = await openai_async_client.chat.completions.create(
79 | model="gpt-3.5-turbo",
80 | messages=[
81 | {"role": "system", "content": "You are a helpful assistant."},
82 | {"role": "user", "content": "What is the capital of Spain?"},
83 | ],
84 | )
85 |
86 | captured = capsys.readouterr()
87 | assert "WARNING: While" not in captured.err
88 | assert completion.choices[0].message.content is not None
89 |
90 |
91 | @pytest.mark.asyncio
92 | async def test_openai_chat_completion_async_stream_with_pl_id(capsys, openai_async_client):
93 | with assert_played("test_openai_chat_completion_async_stream_with_pl_id.yaml"):
94 | completion = await openai_async_client.chat.completions.create(
95 | model="gpt-3.5-turbo",
96 | messages=[
97 | {"role": "system", "content": "You are a helpful assistant."},
98 | {"role": "user", "content": "What is the capital of Japan?"},
99 | ],
100 | stream=True,
101 | return_pl_id=True,
102 | )
103 |
104 | assert "WARNING: While" not in capsys.readouterr().err
105 | pl_id = None
106 | async for _, pl_id in completion:
107 | assert pl_id is None or isinstance(pl_id, int)
108 | assert isinstance(pl_id, int)
109 |
--------------------------------------------------------------------------------
/tests/test_templates_groups_track.py:
--------------------------------------------------------------------------------
1 | from tests.utils.vcr import assert_played
2 |
3 |
4 | def test_track_and_templates(sample_template_name, promptlayer_client, openai_client):
5 | # TODO(dmu) HIGH: Improve asserts in this test
6 | with assert_played("test_track_and_templates.yaml"):
7 | response = promptlayer_client.templates.get(
8 | sample_template_name, {"provider": "openai", "model": "gpt-3.5-turbo"}
9 | )
10 | assert response == {
11 | "id": 4,
12 | "prompt_name": "sample_template",
13 | "tags": ["test"],
14 | "workspace_id": 1,
15 | "commit_message": "test",
16 | "metadata": {
17 | "model": {
18 | "provider": "openai",
19 | "name": "gpt-4o-mini",
20 | "parameters": {
21 | "frequency_penalty": 0,
22 | "max_tokens": 256,
23 | "messages": [{"content": "Hello", "role": "system"}],
24 | "model": "gpt-4o",
25 | "presence_penalty": 0,
26 | "seed": 0,
27 | "temperature": 1,
28 | "top_p": 1,
29 | },
30 | }
31 | },
32 | "prompt_template": {
33 | "messages": [
34 | {
35 | "input_variables": [],
36 | "template_format": "f-string",
37 | "content": [{"type": "text", "text": ""}],
38 | "raw_request_display_role": "",
39 | "dataset_examples": [],
40 | "role": "system",
41 | "name": None,
42 | },
43 | {
44 | "input_variables": [],
45 | "template_format": "f-string",
46 | "content": [{"type": "text", "text": "What is the capital of Japan?"}],
47 | "raw_request_display_role": "",
48 | "dataset_examples": [],
49 | "role": "user",
50 | "name": None,
51 | },
52 | ],
53 | "functions": [],
54 | "tools": None,
55 | "function_call": "none",
56 | "tool_choice": None,
57 | "type": "chat",
58 | "input_variables": [],
59 | "dataset_examples": [],
60 | },
61 | "llm_kwargs": {
62 | "messages": [{"content": "Hello", "role": "system"}],
63 | "model": "gpt-4o",
64 | "frequency_penalty": 0,
65 | "max_tokens": 256,
66 | "presence_penalty": 0,
67 | "seed": 0,
68 | "temperature": 1,
69 | "top_p": 1,
70 | },
71 | "provider_base_url": None,
72 | "version": 1,
73 | "snippets": [],
74 | "warning": None,
75 | }
76 |
77 | llm_kwargs = response["llm_kwargs"].copy()
78 | llm_kwargs.pop("model", None)
79 | _, pl_id = openai_client.chat.completions.create(return_pl_id=True, model="gpt-3.5-turbo", **llm_kwargs)
80 | assert promptlayer_client.track.score(request_id=pl_id, score_name="accuracy", score=10) is not None
81 | assert promptlayer_client.track.metadata(request_id=pl_id, metadata={"test": "test"})
82 |
83 | group_id = promptlayer_client.group.create()
84 | assert isinstance(group_id, int)
85 | assert promptlayer_client.track.group(request_id=pl_id, group_id=group_id)
86 |
87 |
88 | def test_get_all_templates(promptlayer_client):
89 | with assert_played("test_get_all_templates.yaml"):
90 | all_templates = promptlayer_client.templates.all()
91 | assert isinstance(all_templates, list)
92 | assert len(all_templates) > 0
93 |
--------------------------------------------------------------------------------
/tests/utils/mocks.py:
--------------------------------------------------------------------------------
1 | import re
2 | from datetime import datetime
3 | from uuid import UUID
4 |
5 |
6 | class Any:
7 | def __init__(self, *, type_=None, regex=None):
8 | self.type = type_
9 | self.regex = regex if isinstance(regex, (re.Pattern, type(None))) else re.compile(regex)
10 |
11 | def __eq__(self, other):
12 | result = True
13 | if (type_ := self.type) is not None:
14 | result &= isinstance(other, type_)
15 | if regex := self.regex:
16 | result &= bool(regex.match(str(other)))
17 |
18 | return result
19 |
20 | def __ne__(self, other):
21 | return self != other
22 |
23 | def __repr__(self):
24 | return (
25 | f"Any(type_={'None' if self.type is None else self.type.__name__}, "
26 | f"regex={'None' if self.regex is None else self.regex.pattern})"
27 | )
28 |
29 | def __str__(self):
30 | return repr(self)
31 |
32 |
33 | ANY_INT = Any(type_=int)
34 | ANY_BOOL = Any(type_=bool)
35 | ANY_STR = Any(type_=str)
36 | ANY_DATETIME = Any(type_=datetime)
37 | ANY_UUID = Any(type_=UUID)
38 |
--------------------------------------------------------------------------------
/tests/utils/vcr.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | from contextlib import contextmanager
4 | from pathlib import Path
5 |
6 | import vcr
7 | from vcr.record_mode import RecordMode
8 |
9 | logger = logging.getLogger(__name__)
10 |
11 | CASSETTES_PATH = Path(__file__).resolve().parent.parent / "fixtures/cassettes"
12 | VCR_DEFAULT_KWARGS = {
13 | "allow_playback_repeats": False,
14 | "decode_compressed_response": True,
15 | "filter_headers": [
16 | ("Authorization", "sanitized"),
17 | ("x-api-key", "sanitized"),
18 | ],
19 | "filter_post_data_parameters": [("api_key", "sanitized")],
20 | }
21 |
22 |
23 | def is_cassette_recording():
24 | return os.getenv("PROMPTLAYER_IS_CASSETTE_RECORDING", "false").lower() == "true"
25 |
26 |
27 | @contextmanager
28 | def assert_played(cassette_name, should_assert_played=True, play_count=None, **kwargs):
29 | combined_kwargs = VCR_DEFAULT_KWARGS | kwargs
30 | is_cassette_recording_ = is_cassette_recording()
31 | combined_kwargs.setdefault(
32 | "record_mode", RecordMode.ONCE.value if is_cassette_recording_ else RecordMode.NONE.value
33 | )
34 |
35 | vcr_instance = vcr.VCR()
36 | with vcr_instance.use_cassette(str(CASSETTES_PATH / cassette_name), **combined_kwargs) as cassette:
37 | yield cassette
38 | if should_assert_played and not is_cassette_recording_:
39 | if play_count is None:
40 | assert cassette.all_played, "Not all requests have played"
41 | else:
42 | actual_play_count = cassette.play_count
43 | if cassette.play_count != play_count:
44 | play_counts = cassette.play_counts
45 | for index, request in enumerate(cassette.requests):
46 | logger.debug("%s played %s time(s)", request, play_counts[index])
47 | raise AssertionError(f"Expected {play_count}, actually played {actual_play_count}")
48 |
--------------------------------------------------------------------------------