From fa50a98fd8caf820903dad623d00d5ee73fa5529 Mon Sep 17 00:00:00 2001 From: Habibul Rahman Qalbi Date: Mon, 20 Jan 2025 15:46:06 +0700 Subject: [PATCH 1/4] fix: :bug: fix openai.BadRequestError: Error code: 400 Invalid value for 'content': expected a string, got null. --- .../llama_index/llms/openai/utils.py | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/utils.py b/llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/utils.py index 6e48083d24960..4f1c2d582ec37 100644 --- a/llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/utils.py +++ b/llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/utils.py @@ -2,7 +2,13 @@ import os from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union +import openai from deprecated import deprecated +from openai.types.chat import ChatCompletionMessageParam, ChatCompletionMessageToolCall +from openai.types.chat.chat_completion_chunk import ChoiceDeltaToolCall +from openai.types.chat.chat_completion_message import ChatCompletionMessage +from openai.types.chat.chat_completion_token_logprob import ChatCompletionTokenLogprob +from openai.types.completion_choice import Logprobs from tenacity import ( before_sleep_log, retry, @@ -14,20 +20,9 @@ ) from tenacity.stop import stop_base -import openai from llama_index.core.base.llms.generic_utils import get_from_param_or_env -from llama_index.core.base.llms.types import ( - ChatMessage, - ImageBlock, - LogProb, - TextBlock, -) +from llama_index.core.base.llms.types import ChatMessage, ImageBlock, LogProb, TextBlock from llama_index.core.bridge.pydantic import BaseModel -from openai.types.chat import ChatCompletionMessageParam, ChatCompletionMessageToolCall -from openai.types.chat.chat_completion_chunk import ChoiceDeltaToolCall -from openai.types.chat.chat_completion_message import ChatCompletionMessage -from openai.types.chat.chat_completion_token_logprob import ChatCompletionTokenLogprob -from openai.types.completion_choice import Logprobs DEFAULT_OPENAI_API_TYPE = "open_ai" DEFAULT_OPENAI_API_BASE = "https://api.openai.com/v1" @@ -292,20 +287,18 @@ def to_openai_message_dict( msg = f"Unsupported content block type: {type(block).__name__}" raise ValueError(msg) - # NOTE: Sending a blank string to openai will cause an error. - # This will commonly happen with tool calls. - content_txt = None if content_txt == "" else content_txt - # NOTE: Despite what the openai docs say, if the role is ASSISTANT, SYSTEM # or TOOL, 'content' cannot be a list and must be string instead. # Furthermore, if all blocks are text blocks, we can use the content_txt # as the content. This will avoid breaking openai-like APIs. message_dict = { "role": message.role.value, - "content": content_txt - if message.role.value in ("assistant", "tool", "system") - or all(isinstance(block, TextBlock) for block in message.blocks) - else content, + "content": ( + content_txt + if message.role.value in ("assistant", "tool", "system") + or all(isinstance(block, TextBlock) for block in message.blocks) + else content + ), } # TODO: O1 models do not support system prompts From 679dcbee2178c9762f9f6a45fe623cb26743a73a Mon Sep 17 00:00:00 2001 From: Habibul Rahman Qalbi Date: Mon, 20 Jan 2025 15:50:30 +0700 Subject: [PATCH 2/4] test: :white_check_mark: Update and fix typo openai_message_dicts_with_function_calling test --- .../tests/test_openai_utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/llama-index-integrations/llms/llama-index-llms-openai/tests/test_openai_utils.py b/llama-index-integrations/llms/llama-index-llms-openai/tests/test_openai_utils.py index 9f7ec48c5e923..b14e223b1e1e0 100644 --- a/llama-index-integrations/llms/llama-index-llms-openai/tests/test_openai_utils.py +++ b/llama-index-integrations/llms/llama-index-llms-openai/tests/test_openai_utils.py @@ -59,7 +59,7 @@ def chat_messages_with_function_calling() -> List[ChatMessage]: @pytest.fixture() -def openi_message_dicts_with_function_calling() -> List[ChatCompletionMessageParam]: +def openai_message_dicts_with_function_calling() -> List[ChatCompletionMessageParam]: return [ { "role": "user", @@ -67,7 +67,7 @@ def openi_message_dicts_with_function_calling() -> List[ChatCompletionMessagePar }, { "role": "assistant", - "content": None, + "content": "", "function_call": { "name": "get_current_weather", "arguments": '{ "location": "Boston, MA"}', @@ -158,19 +158,19 @@ def test_to_openai_message_dicts_basic_string() -> None: def test_to_openai_message_dicts_function_calling( chat_messages_with_function_calling: List[ChatMessage], - openi_message_dicts_with_function_calling: List[ChatCompletionMessageParam], + openai_message_dicts_with_function_calling: List[ChatCompletionMessageParam], ) -> None: message_dicts = to_openai_message_dicts( chat_messages_with_function_calling, ) - assert message_dicts == openi_message_dicts_with_function_calling + assert message_dicts == openai_message_dicts_with_function_calling def test_from_openai_message_dicts_function_calling( - openi_message_dicts_with_function_calling: List[ChatCompletionMessageParam], + openai_message_dicts_with_function_calling: List[ChatCompletionMessageParam], chat_messages_with_function_calling: List[ChatMessage], ) -> None: - chat_messages = from_openai_message_dicts(openi_message_dicts_with_function_calling) # type: ignore + chat_messages = from_openai_message_dicts(openai_message_dicts_with_function_calling) # type: ignore # assert attributes match for chat_message, chat_message_with_function_calling in zip( From 3b09bff6b577dde9ba65053fbed9af0eaa9da395 Mon Sep 17 00:00:00 2001 From: Habibul Rahman Qalbi Date: Tue, 21 Jan 2025 09:35:30 +0700 Subject: [PATCH 3/4] feat: :sparkles: Only set Content to None if ChatMessage Role == Assistant --- .../llama_index/llms/openai/utils.py | 17 ++++++++++++++++- .../tests/test_openai_utils.py | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/utils.py b/llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/utils.py index 4f1c2d582ec37..220ad8d860ccb 100644 --- a/llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/utils.py +++ b/llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/utils.py @@ -21,7 +21,13 @@ from tenacity.stop import stop_base from llama_index.core.base.llms.generic_utils import get_from_param_or_env -from llama_index.core.base.llms.types import ChatMessage, ImageBlock, LogProb, TextBlock +from llama_index.core.base.llms.types import ( + ChatMessage, + ImageBlock, + LogProb, + MessageRole, + TextBlock, +) from llama_index.core.bridge.pydantic import BaseModel DEFAULT_OPENAI_API_TYPE = "open_ai" @@ -287,6 +293,15 @@ def to_openai_message_dict( msg = f"Unsupported content block type: {type(block).__name__}" raise ValueError(msg) + # NOTE: Sending a null value (None) for Tool Message to OpenAI will cause error + # It's only Allowed to send None if it's an Assistant Message + # Reference: https://platform.openai.com/docs/api-reference/chat/create + content_txt = ( + None + if content_txt == "" and message.role == MessageRole.ASSISTANT + else content_txt + ) + # NOTE: Despite what the openai docs say, if the role is ASSISTANT, SYSTEM # or TOOL, 'content' cannot be a list and must be string instead. # Furthermore, if all blocks are text blocks, we can use the content_txt diff --git a/llama-index-integrations/llms/llama-index-llms-openai/tests/test_openai_utils.py b/llama-index-integrations/llms/llama-index-llms-openai/tests/test_openai_utils.py index b14e223b1e1e0..b829d30b1dcb5 100644 --- a/llama-index-integrations/llms/llama-index-llms-openai/tests/test_openai_utils.py +++ b/llama-index-integrations/llms/llama-index-llms-openai/tests/test_openai_utils.py @@ -67,7 +67,7 @@ def openai_message_dicts_with_function_calling() -> List[ChatCompletionMessagePa }, { "role": "assistant", - "content": "", + "content": None, "function_call": { "name": "get_current_weather", "arguments": '{ "location": "Boston, MA"}', From 3c1c367663116c967a43bafd238efd26288a3bfc Mon Sep 17 00:00:00 2001 From: Logan Markewich Date: Wed, 22 Jan 2025 10:21:42 -0600 Subject: [PATCH 4/4] vbump --- .../llms/llama-index-llms-openai/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llama-index-integrations/llms/llama-index-llms-openai/pyproject.toml b/llama-index-integrations/llms/llama-index-llms-openai/pyproject.toml index efd26805d9b51..e92a58e044b7d 100644 --- a/llama-index-integrations/llms/llama-index-llms-openai/pyproject.toml +++ b/llama-index-integrations/llms/llama-index-llms-openai/pyproject.toml @@ -29,7 +29,7 @@ exclude = ["**/BUILD"] license = "MIT" name = "llama-index-llms-openai" readme = "README.md" -version = "0.3.13" +version = "0.3.14" [tool.poetry.dependencies] python = ">=3.9,<4.0"