Skip to content

Commit f10ec93

Browse files
authored
Fix tool call history not round-tripped in conversation context (#203)
* Fix tool call history not round-tripped in conversation context * Hydrate tool calls and results in DatabaseConversationStore
1 parent 6c654a5 commit f10ec93

File tree

4 files changed

+76
-4
lines changed

4 files changed

+76
-4
lines changed

src/Gateway/Prism/PrismGateway.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,11 @@ public function generateText(
7979
$this->addProviderTools($provider, $request, $tools);
8080
}
8181

82+
$prismMessages = $this->toPrismMessages($messages);
83+
8284
try {
8385
$response = $request
84-
->withMessages($this->toPrismMessages($messages))
86+
->withMessages($prismMessages)
8587
->{$structured ? 'asStructured' : 'asText'}();
8688
} catch (PrismVendorException $e) {
8789
throw PrismException::toAiException($e, $provider, $model);
@@ -106,7 +108,7 @@ public function generateText(
106108
PrismUsage::toLaravelUsage($response->usage),
107109
new Meta($provider->name(), $response->meta->model, $citations),
108110
))->withMessages(
109-
PrismMessages::toLaravelMessages($response->messages)
111+
PrismMessages::toLaravelMessages($response->messages)->slice(count($prismMessages))->values()
110112
)->withSteps(PrismSteps::toLaravelSteps($response->steps, $provider));
111113
}
112114

src/Gateway/Prism/PrismMessages.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
use Prism\Prism\ValueObjects\Messages\AssistantMessage as PrismAssistantMessage;
3232
use Prism\Prism\ValueObjects\Messages\ToolResultMessage as PrismToolResultMessage;
3333
use Prism\Prism\ValueObjects\Messages\UserMessage as PrismUserMessage;
34+
use Prism\Prism\ValueObjects\ToolCall as PrismToolCall;
35+
use Prism\Prism\ValueObjects\ToolResult as PrismToolResult;
3436

3537
class PrismMessages
3638
{
@@ -41,6 +43,18 @@ public static function fromLaravelMessages(Collection $messages): Collection
4143
{
4244
return $messages
4345
->map(function ($message) {
46+
if ($message instanceof ToolResultMessage) {
47+
return new PrismToolResultMessage(
48+
$message->toolResults->map(fn ($toolResult) => new PrismToolResult(
49+
toolCallId: $toolResult->id,
50+
toolName: $toolResult->name,
51+
args: $toolResult->arguments,
52+
result: $toolResult->result,
53+
toolCallResultId: $toolResult->resultId,
54+
))->all()
55+
);
56+
}
57+
4458
$message = Message::tryFrom($message);
4559

4660
if ($message->role === MessageRole::User) {
@@ -51,7 +65,21 @@ public static function fromLaravelMessages(Collection $messages): Collection
5165
}
5266

5367
if ($message->role === MessageRole::Assistant) {
54-
return new PrismAssistantMessage($message->content);
68+
$toolCalls = $message instanceof AssistantMessage && $message->toolCalls->isNotEmpty()
69+
? $message->toolCalls->map(fn ($toolCall) => new PrismToolCall(
70+
id: $toolCall->id,
71+
name: $toolCall->name,
72+
arguments: $toolCall->arguments,
73+
resultId: $toolCall->resultId,
74+
reasoningId: $toolCall->reasoningId,
75+
reasoningSummary: $toolCall->reasoningSummary,
76+
))->all()
77+
: [];
78+
79+
return new PrismAssistantMessage(
80+
$message->content ?? '',
81+
toolCalls: $toolCalls,
82+
);
5583
}
5684
})->filter()->values();
5785
}

src/Providers/Concerns/GeneratesText.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public function prompt(AgentPrompt $prompt): AgentResponse
7373
->withSteps($response->steps)
7474
: (new AgentResponse($invocationId, $response->text, $response->usage, $response->meta))
7575
->withMessages($response->messages)
76+
->withToolCallsAndResults($response->toolCalls, $response->toolResults)
7677
->withSteps($response->steps);
7778
});
7879

src/Storage/DatabaseConversationStore.php

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@
66
use Illuminate\Support\Facades\DB;
77
use Illuminate\Support\Str;
88
use Laravel\Ai\Contracts\ConversationStore;
9+
use Laravel\Ai\Messages\AssistantMessage;
910
use Laravel\Ai\Messages\Message;
11+
use Laravel\Ai\Messages\ToolResultMessage;
1012
use Laravel\Ai\Prompts\AgentPrompt;
1113
use Laravel\Ai\Responses\AgentResponse;
14+
use Laravel\Ai\Responses\Data\ToolCall;
15+
use Laravel\Ai\Responses\Data\ToolResult;
1216

1317
class DatabaseConversationStore implements ConversationStore
1418
{
@@ -107,6 +111,43 @@ public function getLatestConversationMessages(string $conversationId, int $limit
107111
->get()
108112
->reverse()
109113
->values()
110-
->map(fn ($m) => new Message($m->role, $m->content));
114+
->flatMap(function ($record) {
115+
$toolCalls = collect(json_decode($record->tool_calls, true));
116+
$toolResults = collect(json_decode($record->tool_results, true));
117+
118+
if ($record->role === 'user') {
119+
return [new Message('user', $record->content)];
120+
}
121+
122+
if ($toolCalls->isNotEmpty()) {
123+
$messages = [];
124+
125+
$messages[] = new AssistantMessage(
126+
$record->content ?: '',
127+
$toolCalls->map(fn ($toolCall) => new ToolCall(
128+
id: $toolCall['id'],
129+
name: $toolCall['name'],
130+
arguments: $toolCall['arguments'],
131+
resultId: $toolCall['result_id'] ?? null,
132+
))
133+
);
134+
135+
if ($toolResults->isNotEmpty()) {
136+
$messages[] = new ToolResultMessage(
137+
$toolResults->map(fn ($toolResult) => new ToolResult(
138+
id: $toolResult['id'],
139+
name: $toolResult['name'],
140+
arguments: $toolResult['arguments'],
141+
result: $toolResult['result'],
142+
resultId: $toolResult['result_id'] ?? null,
143+
))
144+
);
145+
}
146+
147+
return $messages;
148+
}
149+
150+
return [new AssistantMessage($record->content)];
151+
});
111152
}
112153
}

0 commit comments

Comments
 (0)