Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -206,45 +206,55 @@ protected function prepareGenerateTextParams(array $prompt): array
*/
protected function prepareMessagesParam(array $messages, ?string $systemInstruction = null): array
{
$messagesParam = array_map(
function (Message $message): array {
// Special case: Function response.
$messageParts = $message->getParts();
if (count($messageParts) === 1 && $messageParts[0]->getType()->isFunctionResponse()) {
$functionResponse = $messageParts[0]->getFunctionResponse();
$messagesParam = [];
foreach ($messages as $message) {
// Special case: Function responses must be sent as separate tool messages.
$messageParts = $message->getParts();
if (
!empty($messageParts)
&& count(array_filter(
$messageParts,
static function (MessagePart $part): bool {
return $part->getType()->isFunctionResponse();
}
)) === count($messageParts)
) {
foreach ($messageParts as $messagePart) {
$functionResponse = $messagePart->getFunctionResponse();
if (!$functionResponse) {
// This should be impossible due to class internals, but still needs to be checked.
throw new RuntimeException(
'The function response typed message part must contain a function response.'
);
}
return [
$messagesParam[] = [
'role' => 'tool',
'content' => json_encode($functionResponse->getResponse()),
'tool_call_id' => $functionResponse->getId(),
];
}
$messageData = [
'role' => $this->getMessageRoleString($message->getRole()),
'content' => array_values(array_filter(array_map(
[$this, 'getMessagePartContentData'],
$messageParts
))),
];
continue;
}

// Only include tool_calls if there are any (OpenAI rejects empty arrays).
$toolCalls = array_values(array_filter(array_map(
[$this, 'getMessagePartToolCallData'],
$messageData = [
'role' => $this->getMessageRoleString($message->getRole()),
'content' => array_values(array_filter(array_map(
[$this, 'getMessagePartContentData'],
$messageParts
)));
if (!empty($toolCalls)) {
$messageData['tool_calls'] = $toolCalls;
}
))),
];

return $messageData;
},
$messages
);
// Only include tool_calls if there are any (OpenAI rejects empty arrays).
$toolCalls = array_values(array_filter(array_map(
[$this, 'getMessagePartToolCallData'],
$messageParts
)));
if (!empty($toolCalls)) {
$messageData['tool_calls'] = $toolCalls;
}

$messagesParam[] = $messageData;
}

if ($systemInstruction) {
array_unshift(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,33 @@ public function testPrepareMessagesParamFunctionResponse(): void
$this->assertEquals('call_1', $prepared[0]['tool_call_id']);
}

/**
* Tests prepareMessagesParam() with multiple function responses in one message.
*
* @return void
*/
public function testPrepareMessagesParamMultipleFunctionResponses(): void
{
$message = new Message(
MessageRoleEnum::user(),
[
new MessagePart(new FunctionResponse('call_1', 'first_function', ['result' => 'first'])),
new MessagePart(new FunctionResponse('call_2', 'second_function', ['result' => 'second'])),
]
);
$model = $this->createModel();

$prepared = $model->exposePrepareMessagesParam([$message]);

$this->assertCount(2, $prepared);
$this->assertSame('tool', $prepared[0]['role']);
$this->assertSame(json_encode(['result' => 'first']), $prepared[0]['content']);
$this->assertSame('call_1', $prepared[0]['tool_call_id']);
$this->assertSame('tool', $prepared[1]['role']);
$this->assertSame(json_encode(['result' => 'second']), $prepared[1]['content']);
$this->assertSame('call_2', $prepared[1]['tool_call_id']);
}

/**
* Tests getMessageRoleString() method.
*
Expand Down
Loading