Skip to content

Commit 784c25a

Browse files
committed
fix: Handle prism exceptions to continue tool calls and refactor to reduce duplication
1 parent fa7d043 commit 784c25a

File tree

12 files changed

+339
-310
lines changed

12 files changed

+339
-310
lines changed

src/Concerns/CallsTools.php

Lines changed: 79 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,57 +4,111 @@
44

55
namespace Prism\Prism\Concerns;
66

7+
use Generator;
78
use Illuminate\Support\ItemNotFoundException;
89
use Illuminate\Support\MultipleItemsFoundException;
910
use Prism\Prism\Exceptions\PrismException;
11+
use Prism\Prism\Streaming\EventID;
12+
use Prism\Prism\Streaming\Events\ArtifactEvent;
13+
use Prism\Prism\Streaming\Events\ToolResultEvent;
1014
use Prism\Prism\Tool;
1115
use Prism\Prism\ValueObjects\ToolCall;
1216
use Prism\Prism\ValueObjects\ToolOutput;
1317
use Prism\Prism\ValueObjects\ToolResult;
14-
use Throwable;
1518

1619
trait CallsTools
1720
{
1821
/**
22+
* Execute tools and return results (for non-streaming handlers).
23+
*
1924
* @param Tool[] $tools
2025
* @param ToolCall[] $toolCalls
2126
* @return ToolResult[]
2227
*/
2328
protected function callTools(array $tools, array $toolCalls): array
2429
{
25-
return array_map(
26-
function (ToolCall $toolCall) use ($tools): ToolResult {
30+
$toolResults = [];
31+
32+
// Consume generator to execute all tools and collect results
33+
foreach ($this->generateToolEvents($tools, $toolCalls, EventID::generate(), $toolResults) as $event) {
34+
// Events are discarded for non-streaming handlers
35+
}
36+
37+
return $toolResults;
38+
}
39+
40+
/**
41+
* Generate tool execution events and collect results (for streaming handlers).
42+
*
43+
* @param Tool[] $tools
44+
* @param ToolCall[] $toolCalls
45+
* @param ToolResult[] $toolResults Results are collected into this array by reference
46+
* @return Generator<ToolResultEvent|ArtifactEvent>
47+
*/
48+
protected function generateToolEvents(array $tools, array $toolCalls, string $messageId, array &$toolResults): Generator
49+
{
50+
foreach ($toolCalls as $toolCall) {
51+
try {
2752
$tool = $this->resolveTool($toolCall->name, $tools);
53+
$output = call_user_func_array(
54+
$tool->handle(...),
55+
$toolCall->arguments()
56+
);
2857

29-
try {
30-
$output = call_user_func_array(
31-
$tool->handle(...),
32-
$toolCall->arguments()
33-
);
58+
if (is_string($output)) {
59+
$output = new ToolOutput(result: $output);
60+
}
61+
62+
$toolResult = new ToolResult(
63+
toolCallId: $toolCall->id,
64+
toolName: $toolCall->name,
65+
args: $toolCall->arguments(),
66+
result: $output->result,
67+
toolCallResultId: $toolCall->resultId,
68+
artifacts: $output->artifacts,
69+
);
3470

35-
if (is_string($output)) {
36-
$output = new ToolOutput(result: $output);
37-
}
71+
$toolResults[] = $toolResult;
3872

39-
return new ToolResult(
73+
yield new ToolResultEvent(
74+
id: EventID::generate(),
75+
timestamp: time(),
76+
toolResult: $toolResult,
77+
messageId: $messageId,
78+
success: true
79+
);
80+
81+
foreach ($toolResult->artifacts as $artifact) {
82+
yield new ArtifactEvent(
83+
id: EventID::generate(),
84+
timestamp: time(),
85+
artifact: $artifact,
4086
toolCallId: $toolCall->id,
4187
toolName: $toolCall->name,
42-
args: $toolCall->arguments(),
43-
result: $output->result,
44-
toolCallResultId: $toolCall->resultId,
45-
artifacts: $output->artifacts,
88+
messageId: $messageId,
4689
);
47-
} catch (Throwable $e) {
48-
if ($e instanceof PrismException) {
49-
throw $e;
50-
}
51-
52-
throw PrismException::toolCallFailed($toolCall, $e);
5390
}
91+
} catch (PrismException $e) {
92+
$toolResult = new ToolResult(
93+
toolCallId: $toolCall->id,
94+
toolName: $toolCall->name,
95+
args: $toolCall->arguments(),
96+
result: $e->getMessage(),
97+
toolCallResultId: $toolCall->resultId,
98+
);
5499

55-
},
56-
$toolCalls
57-
);
100+
$toolResults[] = $toolResult;
101+
102+
yield new ToolResultEvent(
103+
id: EventID::generate(),
104+
timestamp: time(),
105+
toolResult: $toolResult,
106+
messageId: $messageId,
107+
success: false,
108+
error: $e->getMessage()
109+
);
110+
}
111+
}
58112
}
59113

60114
/**

src/Providers/Anthropic/Handlers/Stream.php

Lines changed: 1 addition & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use Prism\Prism\Providers\Anthropic\Maps\CitationsMapper;
1515
use Prism\Prism\Providers\Anthropic\ValueObjects\AnthropicStreamState;
1616
use Prism\Prism\Streaming\EventID;
17-
use Prism\Prism\Streaming\Events\ArtifactEvent;
1817
use Prism\Prism\Streaming\Events\CitationEvent;
1918
use Prism\Prism\Streaming\Events\ErrorEvent;
2019
use Prism\Prism\Streaming\Events\ProviderToolEvent;
@@ -29,15 +28,12 @@
2928
use Prism\Prism\Streaming\Events\ThinkingStartEvent;
3029
use Prism\Prism\Streaming\Events\ToolCallDeltaEvent;
3130
use Prism\Prism\Streaming\Events\ToolCallEvent;
32-
use Prism\Prism\Streaming\Events\ToolResultEvent;
3331
use Prism\Prism\Text\Request;
3432
use Prism\Prism\ValueObjects\Citation;
3533
use Prism\Prism\ValueObjects\MessagePartWithCitations;
3634
use Prism\Prism\ValueObjects\Messages\AssistantMessage;
3735
use Prism\Prism\ValueObjects\Messages\ToolResultMessage;
3836
use Prism\Prism\ValueObjects\ToolCall;
39-
use Prism\Prism\ValueObjects\ToolOutput;
40-
use Prism\Prism\ValueObjects\ToolResult;
4137
use Prism\Prism\ValueObjects\Usage;
4238
use Psr\Http\Message\StreamInterface;
4339
use Throwable;
@@ -475,61 +471,7 @@ protected function handleToolCalls(Request $request, int $depth): Generator
475471

476472
// Execute tools and emit results
477473
$toolResults = [];
478-
foreach ($toolCalls as $toolCall) {
479-
try {
480-
$tool = $this->resolveTool($toolCall->name, $request->tools());
481-
$output = call_user_func_array($tool->handle(...), $toolCall->arguments());
482-
483-
if (is_string($output)) {
484-
$output = new ToolOutput(result: $output);
485-
}
486-
487-
$toolResult = new ToolResult(
488-
toolCallId: $toolCall->id,
489-
toolName: $toolCall->name,
490-
args: $toolCall->arguments(),
491-
result: $output->result,
492-
artifacts: $output->artifacts,
493-
);
494-
495-
$toolResults[] = $toolResult;
496-
497-
yield new ToolResultEvent(
498-
id: EventID::generate(),
499-
timestamp: time(),
500-
toolResult: $toolResult,
501-
messageId: $this->state->messageId(),
502-
success: true
503-
);
504-
505-
foreach ($toolResult->artifacts as $artifact) {
506-
yield new ArtifactEvent(
507-
id: EventID::generate(),
508-
timestamp: time(),
509-
artifact: $artifact,
510-
toolCallId: $toolCall->id,
511-
toolName: $toolCall->name,
512-
messageId: $this->state->messageId(),
513-
);
514-
}
515-
} catch (Throwable $e) {
516-
$errorResultObj = new ToolResult(
517-
toolCallId: $toolCall->id,
518-
toolName: $toolCall->name,
519-
args: $toolCall->arguments(),
520-
result: []
521-
);
522-
523-
yield new ToolResultEvent(
524-
id: EventID::generate(),
525-
timestamp: time(),
526-
toolResult: $errorResultObj,
527-
messageId: $this->state->messageId(),
528-
success: false,
529-
error: $e->getMessage()
530-
);
531-
}
532-
}
474+
yield from $this->generateToolEvents($request->tools(), $toolCalls, $this->state->messageId(), $toolResults);
533475

534476
// Add messages to request for next turn
535477
if ($toolResults !== []) {

src/Providers/DeepSeek/Handlers/Stream.php

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
use Prism\Prism\Providers\DeepSeek\Maps\ToolChoiceMap;
2121
use Prism\Prism\Providers\DeepSeek\Maps\ToolMap;
2222
use Prism\Prism\Streaming\EventID;
23-
use Prism\Prism\Streaming\Events\ArtifactEvent;
2423
use Prism\Prism\Streaming\Events\StreamEndEvent;
2524
use Prism\Prism\Streaming\Events\StreamEvent;
2625
use Prism\Prism\Streaming\Events\StreamStartEvent;
@@ -31,7 +30,6 @@
3130
use Prism\Prism\Streaming\Events\ThinkingEvent;
3231
use Prism\Prism\Streaming\Events\ThinkingStartEvent;
3332
use Prism\Prism\Streaming\Events\ToolCallEvent;
34-
use Prism\Prism\Streaming\Events\ToolResultEvent;
3533
use Prism\Prism\Streaming\StreamState;
3634
use Prism\Prism\Text\Request;
3735
use Prism\Prism\ValueObjects\Messages\AssistantMessage;
@@ -356,27 +354,8 @@ protected function handleToolCalls(Request $request, string $text, array $toolCa
356354
);
357355
}
358356

359-
$toolResults = $this->callTools($request->tools(), $mappedToolCalls);
360-
361-
foreach ($toolResults as $result) {
362-
yield new ToolResultEvent(
363-
id: EventID::generate(),
364-
timestamp: time(),
365-
toolResult: $result,
366-
messageId: $this->state->messageId()
367-
);
368-
369-
foreach ($result->artifacts as $artifact) {
370-
yield new ArtifactEvent(
371-
id: EventID::generate(),
372-
timestamp: time(),
373-
artifact: $artifact,
374-
toolCallId: $result->toolCallId,
375-
toolName: $result->toolName,
376-
messageId: $this->state->messageId(),
377-
);
378-
}
379-
}
357+
$toolResults = [];
358+
yield from $this->generateToolEvents($request->tools(), $mappedToolCalls, $this->state->messageId(), $toolResults);
380359

381360
$request->addMessage(new AssistantMessage($text, $mappedToolCalls));
382361
$request->addMessage(new ToolResultMessage($toolResults));

src/Providers/Gemini/Handlers/Stream.php

Lines changed: 1 addition & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
use Prism\Prism\Providers\Gemini\Maps\ToolChoiceMap;
1818
use Prism\Prism\Providers\Gemini\Maps\ToolMap;
1919
use Prism\Prism\Streaming\EventID;
20-
use Prism\Prism\Streaming\Events\ArtifactEvent;
2120
use Prism\Prism\Streaming\Events\StreamEndEvent;
2221
use Prism\Prism\Streaming\Events\StreamEvent;
2322
use Prism\Prism\Streaming\Events\StreamStartEvent;
@@ -28,17 +27,13 @@
2827
use Prism\Prism\Streaming\Events\ThinkingEvent;
2928
use Prism\Prism\Streaming\Events\ThinkingStartEvent;
3029
use Prism\Prism\Streaming\Events\ToolCallEvent;
31-
use Prism\Prism\Streaming\Events\ToolResultEvent;
3230
use Prism\Prism\Streaming\StreamState;
3331
use Prism\Prism\Text\Request;
3432
use Prism\Prism\ValueObjects\Messages\AssistantMessage;
3533
use Prism\Prism\ValueObjects\Messages\ToolResultMessage;
3634
use Prism\Prism\ValueObjects\ToolCall;
37-
use Prism\Prism\ValueObjects\ToolOutput;
38-
use Prism\Prism\ValueObjects\ToolResult;
3935
use Prism\Prism\ValueObjects\Usage;
4036
use Psr\Http\Message\StreamInterface;
41-
use Throwable;
4237

4338
class Stream
4439
{
@@ -294,63 +289,7 @@ protected function handleToolCalls(
294289

295290
// Execute tools and emit results
296291
$toolResults = [];
297-
foreach ($mappedToolCalls as $toolCall) {
298-
try {
299-
$tool = $this->resolveTool($toolCall->name, $request->tools());
300-
$output = call_user_func_array($tool->handle(...), $toolCall->arguments());
301-
302-
if (is_string($output)) {
303-
$output = new ToolOutput(result: $output);
304-
}
305-
306-
$toolResult = new ToolResult(
307-
toolCallId: $toolCall->id,
308-
toolName: $toolCall->name,
309-
args: $toolCall->arguments(),
310-
result: is_array($output->result) ? $output->result : ['result' => $output->result],
311-
artifacts: $output->artifacts,
312-
);
313-
314-
$toolResults[] = $toolResult;
315-
316-
yield new ToolResultEvent(
317-
id: EventID::generate(),
318-
timestamp: time(),
319-
toolResult: $toolResult,
320-
messageId: $this->state->messageId(),
321-
success: true
322-
);
323-
324-
foreach ($toolResult->artifacts as $artifact) {
325-
yield new ArtifactEvent(
326-
id: EventID::generate(),
327-
timestamp: time(),
328-
artifact: $artifact,
329-
toolCallId: $toolCall->id,
330-
toolName: $toolCall->name,
331-
messageId: $this->state->messageId(),
332-
);
333-
}
334-
} catch (Throwable $e) {
335-
$errorResult = new ToolResult(
336-
toolCallId: $toolCall->id,
337-
toolName: $toolCall->name,
338-
args: $toolCall->arguments(),
339-
result: []
340-
);
341-
342-
$toolResults[] = $errorResult;
343-
344-
yield new ToolResultEvent(
345-
id: EventID::generate(),
346-
timestamp: time(),
347-
toolResult: $errorResult,
348-
messageId: $this->state->messageId(),
349-
success: false,
350-
error: $e->getMessage()
351-
);
352-
}
353-
}
292+
yield from $this->generateToolEvents($request->tools(), $mappedToolCalls, $this->state->messageId(), $toolResults);
354293

355294
if ($toolResults !== []) {
356295
$request->addMessage(new AssistantMessage($this->state->currentText(), $mappedToolCalls));

0 commit comments

Comments
 (0)