Skip to content

Commit 63b7554

Browse files
committed
refactor: openai completions message creation
Similar to the corresponding change to responses, we make sure that openai user messages combine related content into the same message where possible
1 parent c3a996d commit 63b7554

File tree

4 files changed

+86
-11
lines changed

4 files changed

+86
-11
lines changed

python/mirascope/llm/clients/openai/completions/_utils/encode.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,29 +46,52 @@ class ChatCompletionCreateKwargs(TypedDict, total=False):
4646
def _encode_user_message(
4747
message: UserMessage,
4848
) -> list[openai_types.ChatCompletionMessageParam]:
49-
"""Convert Mirascope `UserMessage` to a list of OpenAI `ChatCompletionMessageParam`."""
49+
"""Convert Mirascope `UserMessage` to a list of OpenAI `ChatCompletionMessageParam`.
50+
51+
Multiple text content parts are combined into a single user message.
52+
Tool outputs become separate tool messages.
53+
"""
54+
current_content: list[openai_types.ChatCompletionContentPartTextParam] = []
55+
result: list[openai_types.ChatCompletionMessageParam] = []
56+
57+
def flush_message_content() -> None:
58+
nonlocal current_content
59+
if current_content:
60+
content: str | list[openai_types.ChatCompletionContentPartTextParam]
61+
if len(current_content) == 1:
62+
content = current_content[0]["text"]
63+
else:
64+
content = current_content
65+
result.append(
66+
openai_types.ChatCompletionUserMessageParam(
67+
role="user", content=content
68+
)
69+
)
70+
current_content = []
5071

51-
message_params: list[openai_types.ChatCompletionMessageParam] = []
5272
for part in message.content:
5373
if part.type == "text":
54-
message_params.append(
55-
openai_types.ChatCompletionUserMessageParam(
56-
role="user",
57-
content=part.text,
74+
current_content.append(
75+
openai_types.ChatCompletionContentPartTextParam(
76+
text=part.text, type="text"
5877
)
5978
)
6079
elif part.type == "tool_output":
61-
message_params.append(
80+
flush_message_content()
81+
result.append(
6282
openai_types.ChatCompletionToolMessageParam(
6383
role="tool",
6484
content=str(part.value),
6585
tool_call_id=part.id,
6686
)
6787
)
6888
else:
69-
raise NotImplementedError
89+
raise NotImplementedError(
90+
f"Unsupported user content part type: {part.type}"
91+
)
92+
flush_message_content()
7093

71-
return message_params
94+
return result
7295

7396

7497
def _encode_assistant_message(
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
from inline_snapshot import snapshot
22

3-
test_snapshot = snapshot({"exception": {"type": "NotImplementedError", "args": "()"}})
3+
test_snapshot = snapshot(
4+
{
5+
"exception": {
6+
"type": "NotImplementedError",
7+
"args": "('Unsupported user content part type: image',)",
8+
}
9+
}
10+
)
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
from inline_snapshot import snapshot
22

3-
test_snapshot = snapshot({"exception": {"type": "NotImplementedError", "args": "()"}})
3+
test_snapshot = snapshot(
4+
{
5+
"exception": {
6+
"type": "NotImplementedError",
7+
"args": "('Unsupported user content part type: image',)",
8+
}
9+
}
10+
)

python/tests/llm/clients/openai/test_completions_client.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,44 @@
88
from mirascope.llm.clients.openai.completions._utils import encode_request
99

1010

11+
def test_prepare_message_multiple_user_text_parts() -> None:
12+
"""Test preparing an OpenAI request with multiple text parts in a user message.
13+
14+
Included for code coverage.
15+
"""
16+
17+
messages = [
18+
llm.messages.user(["Hello there", "fellow humans"]),
19+
]
20+
assert encode_request(
21+
model_id="gpt-4o", messages=messages, format=None, tools=None, params={}
22+
) == snapshot(
23+
(
24+
[
25+
llm.UserMessage(
26+
content=[
27+
llm.Text(text="Hello there"),
28+
llm.Text(text="fellow humans"),
29+
]
30+
)
31+
],
32+
None,
33+
{
34+
"model": "gpt-4o",
35+
"messages": [
36+
{
37+
"role": "user",
38+
"content": [
39+
{"text": "Hello there", "type": "text"},
40+
{"text": "fellow humans", "type": "text"},
41+
],
42+
}
43+
],
44+
},
45+
)
46+
)
47+
48+
1149
def test_prepare_message_multiple_assistant_text_parts() -> None:
1250
"""Test preparing an OpenAI request with multiple text parts in an assistant message.
1351

0 commit comments

Comments
 (0)