mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-30 02:28:13 +00:00
fix: re-add openai response_format param, add test
This commit is contained in:
@@ -17,6 +17,7 @@ from crewai.events.types.llm_events import LLMCallType
|
|||||||
from crewai.llms.base_llm import BaseLLM
|
from crewai.llms.base_llm import BaseLLM
|
||||||
from crewai.llms.hooks.transport import HTTPTransport
|
from crewai.llms.hooks.transport import HTTPTransport
|
||||||
from crewai.utilities.agent_utils import is_context_length_exceeded
|
from crewai.utilities.agent_utils import is_context_length_exceeded
|
||||||
|
from crewai.utilities.converter import generate_model_description
|
||||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||||
LLMContextLengthExceededError,
|
LLMContextLengthExceededError,
|
||||||
)
|
)
|
||||||
@@ -245,6 +246,16 @@ class OpenAICompletion(BaseLLM):
|
|||||||
if self.is_o1_model and self.reasoning_effort:
|
if self.is_o1_model and self.reasoning_effort:
|
||||||
params["reasoning_effort"] = self.reasoning_effort
|
params["reasoning_effort"] = self.reasoning_effort
|
||||||
|
|
||||||
|
if self.response_format is not None:
|
||||||
|
if isinstance(self.response_format, type) and issubclass(
|
||||||
|
self.response_format, BaseModel
|
||||||
|
):
|
||||||
|
params["response_format"] = generate_model_description(
|
||||||
|
self.response_format
|
||||||
|
)
|
||||||
|
elif isinstance(self.response_format, dict):
|
||||||
|
params["response_format"] = self.response_format
|
||||||
|
|
||||||
if tools:
|
if tools:
|
||||||
params["tools"] = self._convert_tools_for_interference(tools)
|
params["tools"] = self._convert_tools_for_interference(tools)
|
||||||
params["tool_choice"] = "auto"
|
params["tool_choice"] = "auto"
|
||||||
@@ -303,8 +314,11 @@ class OpenAICompletion(BaseLLM):
|
|||||||
"""Handle non-streaming chat completion."""
|
"""Handle non-streaming chat completion."""
|
||||||
try:
|
try:
|
||||||
if response_model:
|
if response_model:
|
||||||
|
parse_params = {
|
||||||
|
k: v for k, v in params.items() if k != "response_format"
|
||||||
|
}
|
||||||
parsed_response = self.client.beta.chat.completions.parse(
|
parsed_response = self.client.beta.chat.completions.parse(
|
||||||
**params,
|
**parse_params,
|
||||||
response_format=response_model,
|
response_format=response_model,
|
||||||
)
|
)
|
||||||
math_reasoning = parsed_response.choices[0].message
|
math_reasoning = parsed_response.choices[0].message
|
||||||
|
|||||||
112
lib/crewai/tests/cassettes/test_openai_response_format_none.yaml
Normal file
112
lib/crewai/tests/cassettes/test_openai_response_format_none.yaml
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
interactions:
|
||||||
|
- request:
|
||||||
|
body: '{"messages":[{"role":"user","content":"Say hello in one word"}],"model":"gpt-4o"}'
|
||||||
|
headers:
|
||||||
|
accept:
|
||||||
|
- application/json
|
||||||
|
accept-encoding:
|
||||||
|
- gzip, deflate, zstd
|
||||||
|
connection:
|
||||||
|
- keep-alive
|
||||||
|
content-length:
|
||||||
|
- '81'
|
||||||
|
content-type:
|
||||||
|
- application/json
|
||||||
|
host:
|
||||||
|
- api.openai.com
|
||||||
|
user-agent:
|
||||||
|
- OpenAI/Python 1.109.1
|
||||||
|
x-stainless-arch:
|
||||||
|
- arm64
|
||||||
|
x-stainless-async:
|
||||||
|
- 'false'
|
||||||
|
x-stainless-lang:
|
||||||
|
- python
|
||||||
|
x-stainless-os:
|
||||||
|
- MacOS
|
||||||
|
x-stainless-package-version:
|
||||||
|
- 1.109.1
|
||||||
|
x-stainless-read-timeout:
|
||||||
|
- '600'
|
||||||
|
x-stainless-retry-count:
|
||||||
|
- '0'
|
||||||
|
x-stainless-runtime:
|
||||||
|
- CPython
|
||||||
|
x-stainless-runtime-version:
|
||||||
|
- 3.12.10
|
||||||
|
method: POST
|
||||||
|
uri: https://api.openai.com/v1/chat/completions
|
||||||
|
response:
|
||||||
|
body:
|
||||||
|
string: !!binary |
|
||||||
|
H4sIAAAAAAAAAwAAAP//jJJNT9wwEIbv+RXunDdVNtoPuteiCoEQJ7SiFYqMPcm6OB7LnvAhtP8d
|
||||||
|
OWE3oYDUiw9+5h2/73heMiHAaNgIUDvJqvU2/1nf1K44vVzePG6vr5cPV2V5/nt7eqG36lcLs6Sg
|
||||||
|
u7+o+KD6rqj1FtmQG7AKKBlT1/l6tSjKYr1a96AljTbJGs/5gvKyKBd5cZIXqzfhjozCCBvxJxNC
|
||||||
|
iJf+TBadxifYiGJ2uGkxRtkgbI5FQkAgm25AxmgiS8cwG6Eix+h612doLX2bwoB1F2Xy5jprJ0A6
|
||||||
|
RyxTtt7W7RvZH41Yanygu/iPFGrjTNxVAWUklx6NTB56us+EuO0Dd+8ygA/Ueq6Y7rF/bl4O7WCc
|
||||||
|
8AgPjImlnWgWs0+aVRpZGhsn8wIl1Q71qByHKzttaAKySeSPXj7rPcQ2rvmf9iNQCj2jrnxAbdT7
|
||||||
|
vGNZwLR+X5UdR9wbhojhwSis2GBI36Cxlp0dNgPic2Rsq9q4BoMPZliP2lfqxwkWSyXna8j22SsA
|
||||||
|
AAD//wMAmJrFFCcDAAA=
|
||||||
|
headers:
|
||||||
|
CF-RAY:
|
||||||
|
- 9a3c18dff8580f53-EWR
|
||||||
|
Connection:
|
||||||
|
- keep-alive
|
||||||
|
Content-Encoding:
|
||||||
|
- gzip
|
||||||
|
Content-Type:
|
||||||
|
- application/json
|
||||||
|
Date:
|
||||||
|
- Mon, 24 Nov 2025 21:46:08 GMT
|
||||||
|
Server:
|
||||||
|
- cloudflare
|
||||||
|
Set-Cookie:
|
||||||
|
- FILTERED
|
||||||
|
Strict-Transport-Security:
|
||||||
|
- max-age=31536000; includeSubDomains; preload
|
||||||
|
Transfer-Encoding:
|
||||||
|
- chunked
|
||||||
|
X-Content-Type-Options:
|
||||||
|
- nosniff
|
||||||
|
access-control-expose-headers:
|
||||||
|
- X-Request-ID
|
||||||
|
alt-svc:
|
||||||
|
- h3=":443"; ma=86400
|
||||||
|
cf-cache-status:
|
||||||
|
- DYNAMIC
|
||||||
|
openai-organization:
|
||||||
|
- FILTERED
|
||||||
|
openai-processing-ms:
|
||||||
|
- '1096'
|
||||||
|
openai-project:
|
||||||
|
- FILTERED
|
||||||
|
openai-version:
|
||||||
|
- '2020-10-01'
|
||||||
|
x-envoy-upstream-service-time:
|
||||||
|
- '1138'
|
||||||
|
x-openai-proxy-wasm:
|
||||||
|
- v0.1
|
||||||
|
x-ratelimit-limit-project-requests:
|
||||||
|
- '10000'
|
||||||
|
x-ratelimit-limit-requests:
|
||||||
|
- '10000'
|
||||||
|
x-ratelimit-limit-tokens:
|
||||||
|
- '30000000'
|
||||||
|
x-ratelimit-remaining-project-requests:
|
||||||
|
- '9999'
|
||||||
|
x-ratelimit-remaining-requests:
|
||||||
|
- '9999'
|
||||||
|
x-ratelimit-remaining-tokens:
|
||||||
|
- '29999992'
|
||||||
|
x-ratelimit-reset-project-requests:
|
||||||
|
- 6ms
|
||||||
|
x-ratelimit-reset-requests:
|
||||||
|
- 6ms
|
||||||
|
x-ratelimit-reset-tokens:
|
||||||
|
- 0s
|
||||||
|
x-request-id:
|
||||||
|
- req_670507131d6c455caf0e8cbc30a1a792
|
||||||
|
status:
|
||||||
|
code: 200
|
||||||
|
message: OK
|
||||||
|
version: 1
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
interactions:
|
||||||
|
- request:
|
||||||
|
body: '{"messages":[{"role":"user","content":"Return a JSON object with a ''status''
|
||||||
|
field set to ''success''"}],"model":"gpt-4o","response_format":{"type":"json_object"}}'
|
||||||
|
headers:
|
||||||
|
accept:
|
||||||
|
- application/json
|
||||||
|
accept-encoding:
|
||||||
|
- gzip, deflate, zstd
|
||||||
|
connection:
|
||||||
|
- keep-alive
|
||||||
|
content-length:
|
||||||
|
- '160'
|
||||||
|
content-type:
|
||||||
|
- application/json
|
||||||
|
host:
|
||||||
|
- api.openai.com
|
||||||
|
user-agent:
|
||||||
|
- OpenAI/Python 1.109.1
|
||||||
|
x-stainless-arch:
|
||||||
|
- arm64
|
||||||
|
x-stainless-async:
|
||||||
|
- 'false'
|
||||||
|
x-stainless-lang:
|
||||||
|
- python
|
||||||
|
x-stainless-os:
|
||||||
|
- MacOS
|
||||||
|
x-stainless-package-version:
|
||||||
|
- 1.109.1
|
||||||
|
x-stainless-read-timeout:
|
||||||
|
- '600'
|
||||||
|
x-stainless-retry-count:
|
||||||
|
- '0'
|
||||||
|
x-stainless-runtime:
|
||||||
|
- CPython
|
||||||
|
x-stainless-runtime-version:
|
||||||
|
- 3.12.10
|
||||||
|
method: POST
|
||||||
|
uri: https://api.openai.com/v1/chat/completions
|
||||||
|
response:
|
||||||
|
body:
|
||||||
|
string: !!binary |
|
||||||
|
H4sIAAAAAAAAA4xSwW6cMBC98xXWnJeKkC274Zr01vbUVopKhLxmACfGdj1D1Wi1/14ZNgtpU6kX
|
||||||
|
hObNe7z3mGMiBOgGSgGql6wGb9Lb9r4191/46eaD+/j509f9Lvs2PP+47u/usIdNZLjDIyp+Yb1T
|
||||||
|
bvAGWTs7wyqgZIyqV7tim+XZrng/AYNr0ERa5zndujTP8m2a7dOsOBN7pxUSlOJ7IoQQx+kZLdoG
|
||||||
|
f0Epss3LZEAi2SGUlyUhIDgTJyCJNLG0DJsFVM4y2sn1sbJCVEAseaQKyvg+KoVEFVT2tGYFbEeS
|
||||||
|
0bQdjVkB0lrHMoae/D6ckdPFoXGdD+5Af1Ch1VZTXweU5Gx0Q+w8TOgpEeJhamJ8FQ58cIPnmt0T
|
||||||
|
Tp/L81kOluoX8OaMsWNplvH11eYNsbpBltrQqkhQUvXYLMyldTk22q2AZBX5by9vac+xte3+R34B
|
||||||
|
lELP2NQ+YKPV67zLWsB4l/9au1Q8GQbC8FMrrFljiL+hwVaOZj4ZoGdiHOpW2w6DD3q+m9bXO8TD
|
||||||
|
tmizYg/JKfkNAAD//wMA0CE0wkADAAA=
|
||||||
|
headers:
|
||||||
|
CF-RAY:
|
||||||
|
- 9a3c18d7de3c80dc-EWR
|
||||||
|
Connection:
|
||||||
|
- keep-alive
|
||||||
|
Content-Encoding:
|
||||||
|
- gzip
|
||||||
|
Content-Type:
|
||||||
|
- application/json
|
||||||
|
Date:
|
||||||
|
- Mon, 24 Nov 2025 21:46:06 GMT
|
||||||
|
Server:
|
||||||
|
- cloudflare
|
||||||
|
Set-Cookie:
|
||||||
|
- FILTERED
|
||||||
|
Strict-Transport-Security:
|
||||||
|
- max-age=31536000; includeSubDomains; preload
|
||||||
|
Transfer-Encoding:
|
||||||
|
- chunked
|
||||||
|
X-Content-Type-Options:
|
||||||
|
- nosniff
|
||||||
|
access-control-expose-headers:
|
||||||
|
- X-Request-ID
|
||||||
|
alt-svc:
|
||||||
|
- h3=":443"; ma=86400
|
||||||
|
cf-cache-status:
|
||||||
|
- DYNAMIC
|
||||||
|
openai-organization:
|
||||||
|
- FILTERED
|
||||||
|
openai-processing-ms:
|
||||||
|
- '424'
|
||||||
|
openai-project:
|
||||||
|
- FILTERED
|
||||||
|
openai-version:
|
||||||
|
- '2020-10-01'
|
||||||
|
x-envoy-upstream-service-time:
|
||||||
|
- '443'
|
||||||
|
x-openai-proxy-wasm:
|
||||||
|
- v0.1
|
||||||
|
x-ratelimit-limit-project-requests:
|
||||||
|
- '10000'
|
||||||
|
x-ratelimit-limit-requests:
|
||||||
|
- '10000'
|
||||||
|
x-ratelimit-limit-tokens:
|
||||||
|
- '30000000'
|
||||||
|
x-ratelimit-remaining-project-requests:
|
||||||
|
- '9999'
|
||||||
|
x-ratelimit-remaining-requests:
|
||||||
|
- '9999'
|
||||||
|
x-ratelimit-remaining-tokens:
|
||||||
|
- '29999983'
|
||||||
|
x-ratelimit-reset-project-requests:
|
||||||
|
- 6ms
|
||||||
|
x-ratelimit-reset-requests:
|
||||||
|
- 6ms
|
||||||
|
x-ratelimit-reset-tokens:
|
||||||
|
- 0s
|
||||||
|
x-request-id:
|
||||||
|
- req_71bc4c9f29f843d6b3788b119850dfde
|
||||||
|
status:
|
||||||
|
code: 200
|
||||||
|
message: OK
|
||||||
|
version: 1
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
interactions:
|
||||||
|
- request:
|
||||||
|
body: '{"messages":[{"role":"user","content":"What is the capital of France? Be
|
||||||
|
concise."}],"model":"gpt-4o","response_format":{"type":"json_schema","json_schema":{"name":"AnswerResponse","strict":true,"schema":{"description":"Response
|
||||||
|
model with structured fields.","properties":{"answer":{"description":"The answer
|
||||||
|
to the question","title":"Answer","type":"string"},"confidence":{"description":"Confidence
|
||||||
|
score between 0 and 1","title":"Confidence","type":"number"}},"required":["answer","confidence"],"title":"AnswerResponse","type":"object","additionalProperties":false}}}}'
|
||||||
|
headers:
|
||||||
|
accept:
|
||||||
|
- application/json
|
||||||
|
accept-encoding:
|
||||||
|
- gzip, deflate, zstd
|
||||||
|
connection:
|
||||||
|
- keep-alive
|
||||||
|
content-length:
|
||||||
|
- '571'
|
||||||
|
content-type:
|
||||||
|
- application/json
|
||||||
|
host:
|
||||||
|
- api.openai.com
|
||||||
|
user-agent:
|
||||||
|
- OpenAI/Python 1.109.1
|
||||||
|
x-stainless-arch:
|
||||||
|
- arm64
|
||||||
|
x-stainless-async:
|
||||||
|
- 'false'
|
||||||
|
x-stainless-lang:
|
||||||
|
- python
|
||||||
|
x-stainless-os:
|
||||||
|
- MacOS
|
||||||
|
x-stainless-package-version:
|
||||||
|
- 1.109.1
|
||||||
|
x-stainless-read-timeout:
|
||||||
|
- '600'
|
||||||
|
x-stainless-retry-count:
|
||||||
|
- '0'
|
||||||
|
x-stainless-runtime:
|
||||||
|
- CPython
|
||||||
|
x-stainless-runtime-version:
|
||||||
|
- 3.12.10
|
||||||
|
method: POST
|
||||||
|
uri: https://api.openai.com/v1/chat/completions
|
||||||
|
response:
|
||||||
|
body:
|
||||||
|
string: !!binary |
|
||||||
|
H4sIAAAAAAAAAwAAAP//jFLLbtswELzrK4g9SwFtyA/pmKCH9pRbUVSBwJArmTVFElyqbWr43wvK
|
||||||
|
jqW0KdALDzs7w5ndPWWMgVZQM5AHEeXgTfHQfemO1f0H8cK73fDp82b/iz6uH4+WnC0hTwz3/A1l
|
||||||
|
fGXdSTd4g1E7e4FlQBExqa5225Kv+W5bTsDgFJpE630sSles+bos+L7g2yvx4LREgpp9zRhj7DS9
|
||||||
|
yaJV+BNqxvPXyoBEokeob02MQXAmVUAQaYrCRshnUDob0U6uTw0ISz8wNFA38CiCpgbyJrV0WqGV
|
||||||
|
2EDN76rqvBQI2I0kkn87GrMAhLUuipR/sv50Rc43s8b1Prhn+oMKnbaaDm1AQc4mYxSdhwk9Z4w9
|
||||||
|
TUMZ3+QEH9zgYxvdEafvqvIiB/MWZnC1uoLRRWEWdb7J35FrFUahDS2mClLIA6qZOq9AjEq7BZAt
|
||||||
|
Qv/t5j3tS3Bt+/+RnwEp0UdUrQ+otHybeG4LmI70X223IU+GgTB81xLbqDGkRSjsxGgu9wP0QhGH
|
||||||
|
ttO2x+CDvhxR51tZ7ZFvpFjtIDtnvwEAAP//AwAvoKedTQMAAA==
|
||||||
|
headers:
|
||||||
|
CF-RAY:
|
||||||
|
- 9a3c18cf7fe04253-EWR
|
||||||
|
Connection:
|
||||||
|
- keep-alive
|
||||||
|
Content-Encoding:
|
||||||
|
- gzip
|
||||||
|
Content-Type:
|
||||||
|
- application/json
|
||||||
|
Date:
|
||||||
|
- Mon, 24 Nov 2025 21:46:05 GMT
|
||||||
|
Server:
|
||||||
|
- cloudflare
|
||||||
|
Set-Cookie:
|
||||||
|
- FILTERED
|
||||||
|
Strict-Transport-Security:
|
||||||
|
- max-age=31536000; includeSubDomains; preload
|
||||||
|
Transfer-Encoding:
|
||||||
|
- chunked
|
||||||
|
X-Content-Type-Options:
|
||||||
|
- nosniff
|
||||||
|
access-control-expose-headers:
|
||||||
|
- X-Request-ID
|
||||||
|
alt-svc:
|
||||||
|
- h3=":443"; ma=86400
|
||||||
|
cf-cache-status:
|
||||||
|
- DYNAMIC
|
||||||
|
openai-organization:
|
||||||
|
- FILTERED
|
||||||
|
openai-processing-ms:
|
||||||
|
- '448'
|
||||||
|
openai-project:
|
||||||
|
- FILTERED
|
||||||
|
openai-version:
|
||||||
|
- '2020-10-01'
|
||||||
|
x-envoy-upstream-service-time:
|
||||||
|
- '465'
|
||||||
|
x-openai-proxy-wasm:
|
||||||
|
- v0.1
|
||||||
|
x-ratelimit-limit-project-requests:
|
||||||
|
- '10000'
|
||||||
|
x-ratelimit-limit-requests:
|
||||||
|
- '10000'
|
||||||
|
x-ratelimit-limit-tokens:
|
||||||
|
- '30000000'
|
||||||
|
x-ratelimit-remaining-project-requests:
|
||||||
|
- '9999'
|
||||||
|
x-ratelimit-remaining-requests:
|
||||||
|
- '9999'
|
||||||
|
x-ratelimit-remaining-tokens:
|
||||||
|
- '29999987'
|
||||||
|
x-ratelimit-reset-project-requests:
|
||||||
|
- 6ms
|
||||||
|
x-ratelimit-reset-requests:
|
||||||
|
- 6ms
|
||||||
|
x-ratelimit-reset-tokens:
|
||||||
|
- 0s
|
||||||
|
x-request-id:
|
||||||
|
- req_765510cb1e614ed6a83e665bf7c5a07b
|
||||||
|
status:
|
||||||
|
code: 200
|
||||||
|
message: OK
|
||||||
|
version: 1
|
||||||
@@ -528,3 +528,50 @@ def test_openai_streaming_with_response_model():
|
|||||||
|
|
||||||
assert "input" not in call_kwargs
|
assert "input" not in call_kwargs
|
||||||
assert "text_format" not in call_kwargs
|
assert "text_format" not in call_kwargs
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_openai_response_format_with_pydantic_model():
|
||||||
|
"""
|
||||||
|
Test that response_format with a Pydantic BaseModel returns structured output.
|
||||||
|
"""
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
class AnswerResponse(BaseModel):
|
||||||
|
"""Response model with structured fields."""
|
||||||
|
|
||||||
|
answer: str = Field(description="The answer to the question")
|
||||||
|
confidence: float = Field(description="Confidence score between 0 and 1")
|
||||||
|
|
||||||
|
llm = LLM(model="gpt-4o", response_format=AnswerResponse)
|
||||||
|
result = llm.call("What is the capital of France? Be concise.")
|
||||||
|
|
||||||
|
assert isinstance(result, AnswerResponse)
|
||||||
|
assert result.answer is not None
|
||||||
|
assert 0 <= result.confidence <= 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_openai_response_format_with_dict():
|
||||||
|
"""
|
||||||
|
Test that response_format with a dict returns JSON output.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
llm = LLM(model="gpt-4o", response_format={"type": "json_object"})
|
||||||
|
result = llm.call("Return a JSON object with a 'status' field set to 'success'")
|
||||||
|
|
||||||
|
parsed = json.loads(result)
|
||||||
|
assert "status" in parsed
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_openai_response_format_none():
|
||||||
|
"""
|
||||||
|
Test that when response_format is None, the API returns plain text.
|
||||||
|
"""
|
||||||
|
llm = LLM(model="gpt-4o", response_format=None)
|
||||||
|
result = llm.call("Say hello in one word")
|
||||||
|
|
||||||
|
assert isinstance(result, str)
|
||||||
|
assert len(result) > 0
|
||||||
|
|||||||
Reference in New Issue
Block a user