Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle OpenAI developer msg #28794

Merged
merged 9 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions libs/core/langchain_core/messages/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,14 @@ def _create_message_from_message_type(
tool_call_id: (str) the tool call id. Default is None.
tool_calls: (list[dict[str, Any]]) the tool calls. Default is None.
id: (str) the id of the message. Default is None.
**additional_kwargs: (dict[str, Any]) additional keyword arguments.
additional_kwargs: (dict[str, Any]) additional keyword arguments.

Returns:
a message of the appropriate type.

Raises:
ValueError: if the message type is not one of "human", "user", "ai",
"assistant", "system", "function", or "tool".
"assistant", "function", "tool", "system", or "developer".
"""
kwargs: dict[str, Any] = {}
if name is not None:
Expand Down Expand Up @@ -261,7 +261,10 @@ def _create_message_from_message_type(
message: BaseMessage = HumanMessage(content=content, **kwargs)
elif message_type in ("ai", "assistant"):
message = AIMessage(content=content, **kwargs)
elif message_type == "system":
elif message_type in ("system", "developer"):
if message_type == "developer":
kwargs["additional_kwargs"] = kwargs.get("additional_kwargs") or {}
kwargs["additional_kwargs"]["__openai_role__"] = "developer"
message = SystemMessage(content=content, **kwargs)
elif message_type == "function":
message = FunctionMessage(content=content, **kwargs)
Expand All @@ -273,7 +276,7 @@ def _create_message_from_message_type(
else:
msg = (
f"Unexpected message type: '{message_type}'. Use one of 'human',"
f" 'user', 'ai', 'assistant', 'function', 'tool', or 'system'."
f" 'user', 'ai', 'assistant', 'function', 'tool', 'system', or 'developer'."
)
msg = create_message(message=msg, error_code=ErrorCode.MESSAGE_COERCION_FAILURE)
raise ValueError(msg)
Expand Down Expand Up @@ -1385,7 +1388,7 @@ def _get_message_openai_role(message: BaseMessage) -> str:
elif isinstance(message, ToolMessage):
return "tool"
elif isinstance(message, SystemMessage):
return "system"
return message.additional_kwargs.get("__openai_role__", "system")
elif isinstance(message, FunctionMessage):
return "function"
elif isinstance(message, ChatMessage):
Expand Down
21 changes: 21 additions & 0 deletions libs/core/tests/unit_tests/messages/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ def test_convert_to_messages() -> None:
message_like: list = [
# BaseMessage
SystemMessage("1"),
SystemMessage("1.1", additional_kwargs={"__openai_role__": "developer"}),
HumanMessage([{"type": "image_url", "image_url": {"url": "2.1"}}], name="2.2"),
AIMessage(
[
Expand Down Expand Up @@ -503,6 +504,7 @@ def test_convert_to_messages() -> None:
ToolMessage("5.1", tool_call_id="5.2", name="5.3"),
# OpenAI dict
{"role": "system", "content": "6"},
{"role": "developer", "content": "6.1"},
{
"role": "user",
"content": [{"type": "image_url", "image_url": {"url": "7.1"}}],
Expand All @@ -526,6 +528,7 @@ def test_convert_to_messages() -> None:
{"role": "tool", "content": "10.1", "tool_call_id": "10.2"},
# Tuple/List
("system", "11.1"),
("developer", "11.2"),
("human", [{"type": "image_url", "image_url": {"url": "12.1"}}]),
(
"ai",
Expand All @@ -551,6 +554,9 @@ def test_convert_to_messages() -> None:
]
expected = [
SystemMessage(content="1"),
SystemMessage(
content="1.1", additional_kwargs={"__openai_role__": "developer"}
),
HumanMessage(
content=[{"type": "image_url", "image_url": {"url": "2.1"}}], name="2.2"
),
Expand Down Expand Up @@ -586,6 +592,9 @@ def test_convert_to_messages() -> None:
),
ToolMessage(content="5.1", name="5.3", tool_call_id="5.2"),
SystemMessage(content="6"),
SystemMessage(
content="6.1", additional_kwargs={"__openai_role__": "developer"}
),
HumanMessage(
content=[{"type": "image_url", "image_url": {"url": "7.1"}}], name="7.2"
),
Expand All @@ -603,6 +612,9 @@ def test_convert_to_messages() -> None:
),
ToolMessage(content="10.1", tool_call_id="10.2"),
SystemMessage(content="11.1"),
SystemMessage(
content="11.2", additional_kwargs={"__openai_role__": "developer"}
),
HumanMessage(content=[{"type": "image_url", "image_url": {"url": "12.1"}}]),
AIMessage(
content=[
Expand Down Expand Up @@ -937,3 +949,12 @@ def test_convert_to_openai_messages_mixed_content_types() -> None:
assert isinstance(result[0]["content"][0], dict)
assert isinstance(result[0]["content"][1], dict)
assert isinstance(result[0]["content"][2], dict)


def test_convert_to_openai_messages_developer() -> None:
messages: list = [
SystemMessage("a", additional_kwargs={"__openai_role__": "developer"}),
{"role": "developer", "content": "a"},
]
result = convert_to_openai_messages(messages)
assert result == [{"role": "developer", "content": "a"}] * 2
27 changes: 22 additions & 5 deletions libs/partners/openai/langchain_openai/chat_models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,17 @@ def _convert_dict_to_message(_dict: Mapping[str, Any]) -> BaseMessage:
tool_calls=tool_calls,
invalid_tool_calls=invalid_tool_calls,
)
elif role == "system":
return SystemMessage(content=_dict.get("content", ""), name=name, id=id_)
elif role in ("system", "developer"):
if role == "developer":
additional_kwargs = {"__openai_role__": role}
else:
additional_kwargs = {}
return SystemMessage(
content=_dict.get("content", ""),
name=name,
id=id_,
additional_kwargs=additional_kwargs,
)
elif role == "function":
return FunctionMessage(
content=_dict.get("content", ""), name=cast(str, _dict.get("name")), id=id_
Expand Down Expand Up @@ -233,7 +242,9 @@ def _convert_message_to_dict(message: BaseMessage) -> dict:
)
message_dict["audio"] = audio
elif isinstance(message, SystemMessage):
message_dict["role"] = "system"
message_dict["role"] = message.additional_kwargs.get(
"__openai_role__", "system"
)
elif isinstance(message, FunctionMessage):
message_dict["role"] = "function"
elif isinstance(message, ToolMessage):
Expand Down Expand Up @@ -284,8 +295,14 @@ def _convert_delta_to_message_chunk(
id=id_,
tool_call_chunks=tool_call_chunks, # type: ignore[arg-type]
)
elif role == "system" or default_class == SystemMessageChunk:
return SystemMessageChunk(content=content, id=id_)
elif role in ("system", "developer") or default_class == SystemMessageChunk:
if role == "developer":
additional_kwargs = {"__openai_role__": "developer"}
else:
additional_kwargs = {}
return SystemMessageChunk(
content=content, id=id_, additional_kwargs=additional_kwargs
)
elif role == "function" or default_class == FunctionMessageChunk:
return FunctionMessageChunk(content=content, name=_dict["name"], id=id_)
elif role == "tool" or default_class == ToolMessageChunk:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1097,3 +1097,16 @@ def test_o1_max_tokens() -> None:
"how are you"
)
assert isinstance(response, AIMessage)


def test_developer_message() -> None:
llm = ChatOpenAI(model="o1", max_tokens=10) # type: ignore[call-arg]
response = llm.invoke(
[
{"role": "developer", "content": "respond in all caps"},
{"role": "user", "content": "HOW ARE YOU"},
]
)
assert isinstance(response, AIMessage)
assert isinstance(response.content, str)
assert response.content.upper() == response.content
32 changes: 32 additions & 0 deletions libs/partners/openai/tests/unit_tests/chat_models/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ def test__convert_dict_to_message_system() -> None:
assert _convert_message_to_dict(expected_output) == message


def test__convert_dict_to_message_developer() -> None:
message = {"role": "developer", "content": "foo"}
result = _convert_dict_to_message(message)
expected_output = SystemMessage(
content="foo", additional_kwargs={"__openai_role__": "developer"}
)
assert result == expected_output
assert _convert_message_to_dict(expected_output) == message


def test__convert_dict_to_message_system_with_name() -> None:
message = {"role": "system", "content": "foo", "name": "test"}
result = _convert_dict_to_message(message)
Expand Down Expand Up @@ -850,3 +860,25 @@ class JokeWithEvaluation(TypedDict):
self_evaluation: SelfEvaluation

llm.with_structured_output(JokeWithEvaluation, method="json_schema")


def test__get_request_payload() -> None:
llm = ChatOpenAI(model="gpt-4o-2024-08-06")
messages: list = [
SystemMessage("hello"),
SystemMessage("bye", additional_kwargs={"__openai_role__": "developer"}),
{"role": "human", "content": "how are you"},
]
expected = {
"messages": [
{"role": "system", "content": "hello"},
{"role": "developer", "content": "bye"},
{"role": "user", "content": "how are you"},
],
"model": "gpt-4o-2024-08-06",
"stream": False,
"n": 1,
"temperature": 0.7,
}
payload = llm._get_request_payload(messages)
assert payload == expected
Loading