mirror of
https://github.com/hwchase17/langchain.git
synced 2025-07-04 04:07:54 +00:00
core[patch], openai[patch]: Handle OpenAI developer msg (#28794)
- Convert developer openai messages to SystemMessage - store additional_kwargs={"__openai_role__": "developer"} so that the correct role can be reconstructed if needed - update ChatOpenAI to read in openai_role --------- Co-authored-by: Erick Friis <erick@langchain.dev>
This commit is contained in:
parent
43b0736a51
commit
4a531437bb
@ -221,14 +221,14 @@ def _create_message_from_message_type(
|
|||||||
tool_call_id: (str) the tool call id. Default is None.
|
tool_call_id: (str) the tool call id. Default is None.
|
||||||
tool_calls: (list[dict[str, Any]]) the tool calls. 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.
|
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:
|
Returns:
|
||||||
a message of the appropriate type.
|
a message of the appropriate type.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: if the message type is not one of "human", "user", "ai",
|
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] = {}
|
kwargs: dict[str, Any] = {}
|
||||||
if name is not None:
|
if name is not None:
|
||||||
@ -261,7 +261,10 @@ def _create_message_from_message_type(
|
|||||||
message: BaseMessage = HumanMessage(content=content, **kwargs)
|
message: BaseMessage = HumanMessage(content=content, **kwargs)
|
||||||
elif message_type in ("ai", "assistant"):
|
elif message_type in ("ai", "assistant"):
|
||||||
message = AIMessage(content=content, **kwargs)
|
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)
|
message = SystemMessage(content=content, **kwargs)
|
||||||
elif message_type == "function":
|
elif message_type == "function":
|
||||||
message = FunctionMessage(content=content, **kwargs)
|
message = FunctionMessage(content=content, **kwargs)
|
||||||
@ -273,7 +276,7 @@ def _create_message_from_message_type(
|
|||||||
else:
|
else:
|
||||||
msg = (
|
msg = (
|
||||||
f"Unexpected message type: '{message_type}'. Use one of 'human',"
|
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)
|
msg = create_message(message=msg, error_code=ErrorCode.MESSAGE_COERCION_FAILURE)
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
@ -1385,7 +1388,7 @@ def _get_message_openai_role(message: BaseMessage) -> str:
|
|||||||
elif isinstance(message, ToolMessage):
|
elif isinstance(message, ToolMessage):
|
||||||
return "tool"
|
return "tool"
|
||||||
elif isinstance(message, SystemMessage):
|
elif isinstance(message, SystemMessage):
|
||||||
return "system"
|
return message.additional_kwargs.get("__openai_role__", "system")
|
||||||
elif isinstance(message, FunctionMessage):
|
elif isinstance(message, FunctionMessage):
|
||||||
return "function"
|
return "function"
|
||||||
elif isinstance(message, ChatMessage):
|
elif isinstance(message, ChatMessage):
|
||||||
|
@ -469,6 +469,7 @@ def test_convert_to_messages() -> None:
|
|||||||
message_like: list = [
|
message_like: list = [
|
||||||
# BaseMessage
|
# BaseMessage
|
||||||
SystemMessage("1"),
|
SystemMessage("1"),
|
||||||
|
SystemMessage("1.1", additional_kwargs={"__openai_role__": "developer"}),
|
||||||
HumanMessage([{"type": "image_url", "image_url": {"url": "2.1"}}], name="2.2"),
|
HumanMessage([{"type": "image_url", "image_url": {"url": "2.1"}}], name="2.2"),
|
||||||
AIMessage(
|
AIMessage(
|
||||||
[
|
[
|
||||||
@ -503,6 +504,7 @@ def test_convert_to_messages() -> None:
|
|||||||
ToolMessage("5.1", tool_call_id="5.2", name="5.3"),
|
ToolMessage("5.1", tool_call_id="5.2", name="5.3"),
|
||||||
# OpenAI dict
|
# OpenAI dict
|
||||||
{"role": "system", "content": "6"},
|
{"role": "system", "content": "6"},
|
||||||
|
{"role": "developer", "content": "6.1"},
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": [{"type": "image_url", "image_url": {"url": "7.1"}}],
|
"content": [{"type": "image_url", "image_url": {"url": "7.1"}}],
|
||||||
@ -526,6 +528,7 @@ def test_convert_to_messages() -> None:
|
|||||||
{"role": "tool", "content": "10.1", "tool_call_id": "10.2"},
|
{"role": "tool", "content": "10.1", "tool_call_id": "10.2"},
|
||||||
# Tuple/List
|
# Tuple/List
|
||||||
("system", "11.1"),
|
("system", "11.1"),
|
||||||
|
("developer", "11.2"),
|
||||||
("human", [{"type": "image_url", "image_url": {"url": "12.1"}}]),
|
("human", [{"type": "image_url", "image_url": {"url": "12.1"}}]),
|
||||||
(
|
(
|
||||||
"ai",
|
"ai",
|
||||||
@ -551,6 +554,9 @@ def test_convert_to_messages() -> None:
|
|||||||
]
|
]
|
||||||
expected = [
|
expected = [
|
||||||
SystemMessage(content="1"),
|
SystemMessage(content="1"),
|
||||||
|
SystemMessage(
|
||||||
|
content="1.1", additional_kwargs={"__openai_role__": "developer"}
|
||||||
|
),
|
||||||
HumanMessage(
|
HumanMessage(
|
||||||
content=[{"type": "image_url", "image_url": {"url": "2.1"}}], name="2.2"
|
content=[{"type": "image_url", "image_url": {"url": "2.1"}}], name="2.2"
|
||||||
),
|
),
|
||||||
@ -586,6 +592,9 @@ def test_convert_to_messages() -> None:
|
|||||||
),
|
),
|
||||||
ToolMessage(content="5.1", name="5.3", tool_call_id="5.2"),
|
ToolMessage(content="5.1", name="5.3", tool_call_id="5.2"),
|
||||||
SystemMessage(content="6"),
|
SystemMessage(content="6"),
|
||||||
|
SystemMessage(
|
||||||
|
content="6.1", additional_kwargs={"__openai_role__": "developer"}
|
||||||
|
),
|
||||||
HumanMessage(
|
HumanMessage(
|
||||||
content=[{"type": "image_url", "image_url": {"url": "7.1"}}], name="7.2"
|
content=[{"type": "image_url", "image_url": {"url": "7.1"}}], name="7.2"
|
||||||
),
|
),
|
||||||
@ -603,6 +612,9 @@ def test_convert_to_messages() -> None:
|
|||||||
),
|
),
|
||||||
ToolMessage(content="10.1", tool_call_id="10.2"),
|
ToolMessage(content="10.1", tool_call_id="10.2"),
|
||||||
SystemMessage(content="11.1"),
|
SystemMessage(content="11.1"),
|
||||||
|
SystemMessage(
|
||||||
|
content="11.2", additional_kwargs={"__openai_role__": "developer"}
|
||||||
|
),
|
||||||
HumanMessage(content=[{"type": "image_url", "image_url": {"url": "12.1"}}]),
|
HumanMessage(content=[{"type": "image_url", "image_url": {"url": "12.1"}}]),
|
||||||
AIMessage(
|
AIMessage(
|
||||||
content=[
|
content=[
|
||||||
@ -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"][0], dict)
|
||||||
assert isinstance(result[0]["content"][1], dict)
|
assert isinstance(result[0]["content"][1], dict)
|
||||||
assert isinstance(result[0]["content"][2], 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
|
||||||
|
@ -139,8 +139,17 @@ def _convert_dict_to_message(_dict: Mapping[str, Any]) -> BaseMessage:
|
|||||||
tool_calls=tool_calls,
|
tool_calls=tool_calls,
|
||||||
invalid_tool_calls=invalid_tool_calls,
|
invalid_tool_calls=invalid_tool_calls,
|
||||||
)
|
)
|
||||||
elif role == "system":
|
elif role in ("system", "developer"):
|
||||||
return SystemMessage(content=_dict.get("content", ""), name=name, id=id_)
|
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":
|
elif role == "function":
|
||||||
return FunctionMessage(
|
return FunctionMessage(
|
||||||
content=_dict.get("content", ""), name=cast(str, _dict.get("name")), id=id_
|
content=_dict.get("content", ""), name=cast(str, _dict.get("name")), id=id_
|
||||||
@ -233,7 +242,9 @@ def _convert_message_to_dict(message: BaseMessage) -> dict:
|
|||||||
)
|
)
|
||||||
message_dict["audio"] = audio
|
message_dict["audio"] = audio
|
||||||
elif isinstance(message, SystemMessage):
|
elif isinstance(message, SystemMessage):
|
||||||
message_dict["role"] = "system"
|
message_dict["role"] = message.additional_kwargs.get(
|
||||||
|
"__openai_role__", "system"
|
||||||
|
)
|
||||||
elif isinstance(message, FunctionMessage):
|
elif isinstance(message, FunctionMessage):
|
||||||
message_dict["role"] = "function"
|
message_dict["role"] = "function"
|
||||||
elif isinstance(message, ToolMessage):
|
elif isinstance(message, ToolMessage):
|
||||||
@ -284,8 +295,14 @@ def _convert_delta_to_message_chunk(
|
|||||||
id=id_,
|
id=id_,
|
||||||
tool_call_chunks=tool_call_chunks, # type: ignore[arg-type]
|
tool_call_chunks=tool_call_chunks, # type: ignore[arg-type]
|
||||||
)
|
)
|
||||||
elif role == "system" or default_class == SystemMessageChunk:
|
elif role in ("system", "developer") or default_class == SystemMessageChunk:
|
||||||
return SystemMessageChunk(content=content, id=id_)
|
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:
|
elif role == "function" or default_class == FunctionMessageChunk:
|
||||||
return FunctionMessageChunk(content=content, name=_dict["name"], id=id_)
|
return FunctionMessageChunk(content=content, name=_dict["name"], id=id_)
|
||||||
elif role == "tool" or default_class == ToolMessageChunk:
|
elif role == "tool" or default_class == ToolMessageChunk:
|
||||||
|
@ -1097,3 +1097,16 @@ def test_o1_max_tokens() -> None:
|
|||||||
"how are you"
|
"how are you"
|
||||||
)
|
)
|
||||||
assert isinstance(response, AIMessage)
|
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
|
||||||
|
@ -100,6 +100,16 @@ def test__convert_dict_to_message_system() -> None:
|
|||||||
assert _convert_message_to_dict(expected_output) == message
|
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:
|
def test__convert_dict_to_message_system_with_name() -> None:
|
||||||
message = {"role": "system", "content": "foo", "name": "test"}
|
message = {"role": "system", "content": "foo", "name": "test"}
|
||||||
result = _convert_dict_to_message(message)
|
result = _convert_dict_to_message(message)
|
||||||
@ -850,3 +860,25 @@ def test_nested_structured_output_strict() -> None:
|
|||||||
self_evaluation: SelfEvaluation
|
self_evaluation: SelfEvaluation
|
||||||
|
|
||||||
llm.with_structured_output(JokeWithEvaluation, method="json_schema")
|
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…
Reference in New Issue
Block a user