From 0096488836b5e844c8e68d54923da7ee93f70977 Mon Sep 17 00:00:00 2001 From: zhangshulin Date: Mon, 2 Dec 2024 10:23:09 +0800 Subject: [PATCH] Add Baidu Provider Support --- aisuite/providers/baidu_provider.py | 40 +++++++ guides/baidu.md | 52 +++++++++ poetry.lock | 155 ++++++++++++++++++++++++- pyproject.toml | 2 + tests/providers/test_baidu_provider.py | 39 +++++++ 5 files changed, 282 insertions(+), 6 deletions(-) create mode 100644 aisuite/providers/baidu_provider.py create mode 100644 guides/baidu.md create mode 100644 tests/providers/test_baidu_provider.py diff --git a/aisuite/providers/baidu_provider.py b/aisuite/providers/baidu_provider.py new file mode 100644 index 00000000..3248733a --- /dev/null +++ b/aisuite/providers/baidu_provider.py @@ -0,0 +1,40 @@ +import os +import qianfan +from aisuite.provider import Provider +from aisuite.framework import ChatCompletionResponse + + +class BaiduProvider(Provider): + """BaiduProvider is a class that provides an interface to the Baidu's model.""" + + def __init__(self, **config): + os.environ["QIANFAN_ACCESS_KEY"] = config.get("access_key") or os.getenv( + "QIANFAN_ACCESS_KEY" + ) + os.environ["QIANFAN_SECRET_KEY"] = config.get("secret_key") or os.getenv( + "QIANFAN_SECRET_KEY" + ) + + if not os.getenv("QIANFAN_ACCESS_KEY"): + raise EnvironmentError( + "Qanfan access key is missing. Please provide it in the config or set the QIANFAN_ACCESS_KEY environment variable." + ) + if not os.getenv("QIANFAN_SECRET_KEY"): + raise EnvironmentError( + "Qanfan secret key is missing. Please provide it in the config or set the QIANFAN_SECRET_KEY environment variable." + ) + + self.client = qianfan.ChatCompletion() + + def chat_completions_create(self, model, messages, **kwargs): + """Send a chat completion request to the Baidu's model.""" + + response = self.client.do(model=model, messages=messages, **kwargs) + return self.normalize_response(response) + + def normalize_response(self, response): + """Normalize the response from Qianfan to match OpenAI's response format.""" + + openai_response = ChatCompletionResponse() + openai_response.choices[0].message.content = response["body"]["result"] + return openai_response diff --git a/guides/baidu.md b/guides/baidu.md new file mode 100644 index 00000000..cfff5861 --- /dev/null +++ b/guides/baidu.md @@ -0,0 +1,52 @@ +# Baidu + +To use Baidu's model, you must log in to the [Baidu AI Cloud Qianfan Large Model Service and Development Platform](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) to obtain an Access Key and Secret Key. After obtaining these keys, configure them as environment variables using the following approach: + +```shell +export QIANFAN_ACCESS_KEY="your-access-key" + +export QIANFAN_SECRET_KEY="your-secret-key" +``` + +## Create a Chat Completion + +### Example Using pip: + +```shell +pip install aisuite[all] +``` + +### Example with poetry: + +```shell +poetry add aisuite +``` + +### In Your Code: + +```python +import aisuite as ai + +# Initialize the client +client = ai.Client() + +# Set provider and model ID +provider = "baidu" +model_id = "ERNIE-3.5-8K" + +# Prepare messages +messages = [ + {"role": "user", "content": "Hello!"}, +] + +# Make a chat completion request +response = client.chat.completions.create( + model=f"{provider}:{model_id}", + messages=messages, +) + +# Output the response +print(response.choices[0].message.content) +``` + +Happy coding! If you would like to contribute, please read our [Contributing Guide](../CONTRIBUTING.md). diff --git a/poetry.lock b/poetry.lock index 27aa1cc1..f8079018 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "aiohttp" @@ -96,6 +96,17 @@ yarl = ">=1.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns", "brotlicffi"] +[[package]] +name = "aiolimiter" +version = "1.2.0" +description = "asyncio rate limiter, a leaky bucket implementation" +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "aiolimiter-1.2.0-py3-none-any.whl", hash = "sha256:e3fc486a4506248cfdd1f3976920459945944518bbb1d1e6b2be1060232829e2"}, + {file = "aiolimiter-1.2.0.tar.gz", hash = "sha256:761455d26df0d7a393f78bd39b022579e02ca5a65beb303a67bed2ded2f740ac"}, +] + [[package]] name = "aiosignal" version = "1.3.1" @@ -359,6 +370,22 @@ files = [ {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] +[[package]] +name = "bce-python-sdk" +version = "0.9.23" +description = "BCE SDK for python" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,<4,>=2.7" +files = [ + {file = "bce_python_sdk-0.9.23-py3-none-any.whl", hash = "sha256:8debe21a040e00060f6044877d594765ed7b18bc765c6bf16b878bca864140a3"}, + {file = "bce_python_sdk-0.9.23.tar.gz", hash = "sha256:19739fed5cd0725356fc5ffa2acbdd8fb23f2a81edb91db21a03174551d0cf41"}, +] + +[package.dependencies] +future = ">=0.6.0" +pycryptodome = ">=3.8.0" +six = ">=1.4.0" + [[package]] name = "bcrypt" version = "4.2.0" @@ -883,6 +910,24 @@ traitlets = ">=4" [package.extras] test = ["pytest"] +[[package]] +name = "dashscope" +version = "1.20.13" +description = "dashscope client sdk library" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "dashscope-1.20.13-py3-none-any.whl", hash = "sha256:68fb7e9ffa260ebba7188520ca3e462b651ead0d5ee470ccd510c84856be7465"}, +] + +[package.dependencies] +aiohttp = "*" +requests = "*" +websocket-client = "*" + +[package.extras] +tokenizer = ["tiktoken"] + [[package]] name = "datasets" version = "2.20.0" @@ -1012,6 +1057,17 @@ files = [ graph = ["objgraph (>=1.7.2)"] profile = ["gprof2dot (>=2022.7.29)"] +[[package]] +name = "diskcache" +version = "5.6.3" +description = "Disk Cache -- Disk and file backed persistent cache." +optional = false +python-versions = ">=3" +files = [ + {file = "diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19"}, + {file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"}, +] + [[package]] name = "distlib" version = "0.3.8" @@ -1346,6 +1402,17 @@ test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe, test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] tqdm = ["tqdm"] +[[package]] +name = "future" +version = "1.0.0" +description = "Clean single-source support for Python 3 and 2" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216"}, + {file = "future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05"}, +] + [[package]] name = "google-api-core" version = "2.19.1" @@ -1361,12 +1428,12 @@ files = [ google-auth = ">=2.14.1,<3.0.dev0" googleapis-common-protos = ">=1.56.2,<2.0.dev0" grpcio = [ - {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ - {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, ] proto-plus = ">=1.22.3,<2.0.0dev" protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" @@ -3650,9 +3717,9 @@ files = [ [package.dependencies] numpy = [ + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, {version = ">=1.22.4", markers = "python_version < \"3.11\""}, {version = ">=1.23.2", markers = "python_version == \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -4120,6 +4187,47 @@ files = [ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +[[package]] +name = "pycryptodome" +version = "3.21.0" +description = "Cryptographic library for Python" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pycryptodome-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dad9bf36eda068e89059d1f07408e397856be9511d7113ea4b586642a429a4fd"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a1752eca64c60852f38bb29e2c86fca30d7672c024128ef5d70cc15868fa10f4"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ba4cc304eac4d4d458f508d4955a88ba25026890e8abff9b60404f76a62c55e"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cb087b8612c8a1a14cf37dd754685be9a8d9869bed2ffaaceb04850a8aeef7e"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:26412b21df30b2861424a6c6d5b1d8ca8107612a4cfa4d0183e71c5d200fb34a"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-win32.whl", hash = "sha256:cc2269ab4bce40b027b49663d61d816903a4bd90ad88cb99ed561aadb3888dd3"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:0fa0a05a6a697ccbf2a12cec3d6d2650b50881899b845fac6e87416f8cb7e87d"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6cce52e196a5f1d6797ff7946cdff2038d3b5f0aba4a43cb6bf46b575fd1b5bb"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:a915597ffccabe902e7090e199a7bf7a381c5506a747d5e9d27ba55197a2c568"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e74c522d630766b03a836c15bff77cb657c5fdf098abf8b1ada2aebc7d0819"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:a3804675283f4764a02db05f5191eb8fec2bb6ca34d466167fc78a5f05bbe6b3"}, + {file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2480ec2c72438430da9f601ebc12c518c093c13111a5c1644c82cdfc2e50b1e4"}, + {file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:de18954104667f565e2fbb4783b56667f30fb49c4d79b346f52a29cb198d5b6b"}, + {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de4b7263a33947ff440412339cb72b28a5a4c769b5c1ca19e33dd6cd1dcec6e"}, + {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0714206d467fc911042d01ea3a1847c847bc10884cf674c82e12915cfe1649f8"}, + {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d85c1b613121ed3dbaa5a97369b3b757909531a959d229406a75b912dd51dd1"}, + {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8898a66425a57bcf15e25fc19c12490b87bd939800f39a03ea2de2aea5e3611a"}, + {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:932c905b71a56474bff8a9c014030bc3c882cee696b448af920399f730a650c2"}, + {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93"}, + {file = "pycryptodome-3.21.0-cp36-abi3-win32.whl", hash = "sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764"}, + {file = "pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53"}, + {file = "pycryptodome-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca"}, + {file = "pycryptodome-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d5ebe0763c982f069d3877832254f64974139f4f9655058452603ff559c482e8"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee86cbde706be13f2dec5a42b52b1c1d1cbb90c8e405c68d0755134735c8dc6"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fd54003ec3ce4e0f16c484a10bc5d8b9bd77fa662a12b85779a2d2d85d67ee0"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5dfafca172933506773482b0e18f0cd766fd3920bd03ec85a283df90d8a17bc6"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:590ef0898a4b0a15485b05210b4a1c9de8806d3ad3d47f74ab1dc07c67a6827f"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35e442630bc4bc2e1878482d6f59ea22e280d7121d7adeaedba58c23ab6386b"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff99f952db3db2fbe98a0b355175f93ec334ba3d01bbde25ad3a5a33abc02b58"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8acd7d34af70ee63f9a849f957558e49a98f8f1634f86a59d2be62bb8e93f71c"}, + {file = "pycryptodome-3.21.0.tar.gz", hash = "sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297"}, +] + [[package]] name = "pydantic" version = "2.8.2" @@ -4135,8 +4243,8 @@ files = [ annotated-types = ">=0.4.0" pydantic-core = "2.20.1" typing-extensions = [ - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, ] [package.extras] @@ -4573,6 +4681,41 @@ files = [ [package.dependencies] cffi = {version = "*", markers = "implementation_name == \"pypy\""} +[[package]] +name = "qianfan" +version = "0.4.12.1" +description = "文心千帆大模型平台 Python SDK" +optional = false +python-versions = "<4,>=3.7" +files = [ + {file = "qianfan-0.4.12.1-py3-none-any.whl", hash = "sha256:70a3f40b5bb683f6504fb0b7ebf7facce5b8cd3dbedf0344ea1b71ed0dcebae6"}, + {file = "qianfan-0.4.12.1.tar.gz", hash = "sha256:0fc790e77ac7af5f5c8e0a8cd826f639703bbbeb5810562a8d5e4a70650c4cfe"}, +] + +[package.dependencies] +aiohttp = ">=3.7.0" +aiolimiter = ">=1.1.0" +bce-python-sdk = ">=0.8.79" +cachetools = ">=5.0.0" +diskcache = ">=5.6.3" +multiprocess = ">=0.70.12" +prompt-toolkit = ">=3.0.38" +pydantic = ">=1.0" +python-dotenv = {version = ">=1.0", markers = "python_version >= \"3.8\""} +pyyaml = ">=6.0.1,<7.0.0" +requests = ">=2.24" +rich = ">=13.0.0" +tenacity = ">=8.2.3,<9.0.0" +typer = ">=0.9.0" +typing-extensions = {version = ">=4.0.0", markers = "python_full_version <= \"3.10.0\""} + +[package.extras] +all = ["emoji (>=2.2.0)", "fastapi (>=0.85.0)", "filelock (>=3.7.0)", "ijson (>=3.0)", "langchain (>=0.1.10)", "langchain-community (>=0.2.0)", "locust (>=2.9.0)", "ltp (>=4.2.0)", "numpy (<1.22.0)", "numpy (>=1.22.0)", "pyarrow (<=12.0.1)", "pyarrow (>=14.0.1)", "python-dateutil (>=2.8.2,<3.0.0)", "sentencepiece (>=0.1.98)", "tabulate (>=0.9.0)", "torch (<=1.13.1)", "torch (>=1.4.0)", "uvicorn (>=0.15.0)"] +dataset-base = ["filelock (>=3.7.0)", "ijson (>=3.0)", "locust (>=2.9.0)", "numpy (<1.22.0)", "numpy (>=1.22.0)", "pyarrow (<=12.0.1)", "pyarrow (>=14.0.1)", "python-dateutil (>=2.8.2,<3.0.0)", "tabulate (>=0.9.0)"] +langchain = ["langchain (>=0.1.10)", "langchain-community (>=0.2.0)"] +local-data-clean = ["emoji (>=2.2.0)", "filelock (>=3.7.0)", "ijson (>=3.0)", "locust (>=2.9.0)", "ltp (>=4.2.0)", "numpy (<1.22.0)", "numpy (>=1.22.0)", "pyarrow (<=12.0.1)", "pyarrow (>=14.0.1)", "python-dateutil (>=2.8.2,<3.0.0)", "sentencepiece (>=0.1.98)", "tabulate (>=0.9.0)", "torch (<=1.13.1)", "torch (>=1.4.0)"] +openai = ["fastapi (>=0.85.0)", "uvicorn (>=0.15.0)"] + [[package]] name = "referencing" version = "0.35.1" @@ -6492,4 +6635,4 @@ openai = ["openai"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "e455ddfd148ffae77e7b9ac196792b38c7cc545c0866cca2aaae227f4a4201df" +content-hash = "7ec6d0abd1cf0facc33edfe29d7ef0a4124c0f549d18d01a2d3d88d0c513e913" diff --git a/pyproject.toml b/pyproject.toml index 8ae9295b..39e77524 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,8 @@ chromadb = "^0.5.4" sentence-transformers = "^3.0.1" datasets = "^2.20.0" vertexai = "^1.63.0" +dashscope = "^1.20.13" +qianfan = "^0.4.12.1" [build-system] requires = ["poetry-core"] diff --git a/tests/providers/test_baidu_provider.py b/tests/providers/test_baidu_provider.py new file mode 100644 index 00000000..7046c881 --- /dev/null +++ b/tests/providers/test_baidu_provider.py @@ -0,0 +1,39 @@ +from unittest.mock import MagicMock, patch +import pytest +from aisuite.providers.baidu_provider import BaiduProvider + + +@pytest.fixture(autouse=True) +def set_api_key_env_var(monkeypatch): + """Fixture to set environment variables for tests.""" + monkeypatch.setenv("QIANFAN_ACCESS_KEY", "test-access-key") + monkeypatch.setenv("QIANFAN_SECRET_KEY", "test-secret-key") + + +def test_baidu_provider(): + """High-level test that the provider is initialized and chat completions are requested successfully.""" + + user_greeting = "Hello!" + message_history = [{"role": "user", "content": user_greeting}] + selected_model = "ERNIE-3.5-8K" + chosen_temperature = 0 + response_text_content = "mocked-text-response-from-model" + + provider = BaiduProvider() + mock_response = MagicMock() + mock_response = {"body": {"result": response_text_content}} + + with patch.object(provider.client, "do", return_value=mock_response) as mock_create: + response = provider.chat_completions_create( + messages=message_history, + model=selected_model, + temperature=chosen_temperature, + ) + + mock_create.assert_called_with( + messages=message_history, + model=selected_model, + temperature=chosen_temperature, + ) + + assert response.choices[0].message.content == response_text_content