Skip to content

Commit 6236fcd

Browse files
committed
fix: Handle prism exceptions to continue tool calls and refactor to reduce duplication
1 parent a0f65bc commit 6236fcd

File tree

12 files changed

+339
-309
lines changed

12 files changed

+339
-309
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->callToolsAndYieldEvents($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 callToolsAndYieldEvents(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;
@@ -31,15 +30,12 @@
3130
use Prism\Prism\Streaming\Events\ThinkingStartEvent;
3231
use Prism\Prism\Streaming\Events\ToolCallDeltaEvent;
3332
use Prism\Prism\Streaming\Events\ToolCallEvent;
34-
use Prism\Prism\Streaming\Events\ToolResultEvent;
3533
use Prism\Prism\Text\Request;
3634
use Prism\Prism\ValueObjects\Citation;
3735
use Prism\Prism\ValueObjects\MessagePartWithCitations;
3836
use Prism\Prism\ValueObjects\Messages\AssistantMessage;
3937
use Prism\Prism\ValueObjects\Messages\ToolResultMessage;
4038
use Prism\Prism\ValueObjects\ToolCall;
41-
use Prism\Prism\ValueObjects\ToolOutput;
42-
use Prism\Prism\ValueObjects\ToolResult;
4339
use Prism\Prism\ValueObjects\Usage;
4440
use Psr\Http\Message\StreamInterface;
4541
use Throwable;
@@ -495,61 +491,7 @@ protected function handleToolCalls(Request $request, int $depth): Generator
495491

496492
// Execute tools and emit results
497493
$toolResults = [];
498-
foreach ($toolCalls as $toolCall) {
499-
try {
500-
$tool = $this->resolveTool($toolCall->name, $request->tools());
501-
$output = call_user_func_array($tool->handle(...), $toolCall->arguments());
502-
503-
if (is_string($output)) {
504-
$output = new ToolOutput(result: $output);
505-
}
506-
507-
$toolResult = new ToolResult(
508-
toolCallId: $toolCall->id,
509-
toolName: $toolCall->name,
510-
args: $toolCall->arguments(),
511-
result: $output->result,
512-
artifacts: $output->artifacts,
513-
);
514-
515-
$toolResults[] = $toolResult;
516-
517-
yield new ToolResultEvent(
518-
id: EventID::generate(),
519-
timestamp: time(),
520-
toolResult: $toolResult,
521-
messageId: $this->state->messageId(),
522-
success: true
523-
);
524-
525-
foreach ($toolResult->artifacts as $artifact) {
526-
yield new ArtifactEvent(
527-
id: EventID::generate(),
528-
timestamp: time(),
529-
artifact: $artifact,
530-
toolCallId: $toolCall->id,
531-
toolName: $toolCall->name,
532-
messageId: $this->state->messageId(),
533-
);
534-
}
535-
} catch (Throwable $e) {
536-
$errorResultObj = new ToolResult(
537-
toolCallId: $toolCall->id,
538-
toolName: $toolCall->name,
539-
args: $toolCall->arguments(),
540-
result: []
541-
);
542-
543-
yield new ToolResultEvent(
544-
id: EventID::generate(),
545-
timestamp: time(),
546-
toolResult: $errorResultObj,
547-
messageId: $this->state->messageId(),
548-
success: false,
549-
error: $e->getMessage()
550-
);
551-
}
552-
}
494+
yield from $this->callToolsAndYieldEvents($request->tools(), $toolCalls, $this->state->messageId(), $toolResults);
553495

554496
// Add messages to request for next turn
555497
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\StepFinishEvent;
2524
use Prism\Prism\Streaming\Events\StepStartEvent;
2625
use Prism\Prism\Streaming\Events\StreamEndEvent;
@@ -33,7 +32,6 @@
3332
use Prism\Prism\Streaming\Events\ThinkingEvent;
3433
use Prism\Prism\Streaming\Events\ThinkingStartEvent;
3534
use Prism\Prism\Streaming\Events\ToolCallEvent;
36-
use Prism\Prism\Streaming\Events\ToolResultEvent;
3735
use Prism\Prism\Streaming\StreamState;
3836
use Prism\Prism\Text\Request;
3937
use Prism\Prism\ValueObjects\Messages\AssistantMessage;
@@ -373,27 +371,8 @@ protected function handleToolCalls(Request $request, string $text, array $toolCa
373371
);
374372
}
375373

376-
$toolResults = $this->callTools($request->tools(), $mappedToolCalls);
377-
378-
foreach ($toolResults as $result) {
379-
yield new ToolResultEvent(
380-
id: EventID::generate(),
381-
timestamp: time(),
382-
toolResult: $result,
383-
messageId: $this->state->messageId()
384-
);
385-
386-
foreach ($result->artifacts as $artifact) {
387-
yield new ArtifactEvent(
388-
id: EventID::generate(),
389-
timestamp: time(),
390-
artifact: $artifact,
391-
toolCallId: $result->toolCallId,
392-
toolName: $result->toolName,
393-
messageId: $this->state->messageId(),
394-
);
395-
}
396-
}
374+
$toolResults = [];
375+
yield from $this->callToolsAndYieldEvents($request->tools(), $mappedToolCalls, $this->state->messageId(), $toolResults);
397376

398377
$request->addMessage(new AssistantMessage($text, $mappedToolCalls));
399378
$request->addMessage(new ToolResultMessage($toolResults));

src/Providers/Gemini/Handlers/Stream.php

Lines changed: 1 addition & 61 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\StepFinishEvent;
2221
use Prism\Prism\Streaming\Events\StepStartEvent;
2322
use Prism\Prism\Streaming\Events\StreamEndEvent;
@@ -30,14 +29,11 @@
3029
use Prism\Prism\Streaming\Events\ThinkingEvent;
3130
use Prism\Prism\Streaming\Events\ThinkingStartEvent;
3231
use Prism\Prism\Streaming\Events\ToolCallEvent;
33-
use Prism\Prism\Streaming\Events\ToolResultEvent;
3432
use Prism\Prism\Streaming\StreamState;
3533
use Prism\Prism\Text\Request;
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;
@@ -313,63 +309,7 @@ protected function handleToolCalls(
313309

314310
// Execute tools and emit results
315311
$toolResults = [];
316-
foreach ($mappedToolCalls as $toolCall) {
317-
try {
318-
$tool = $this->resolveTool($toolCall->name, $request->tools());
319-
$output = call_user_func_array($tool->handle(...), $toolCall->arguments());
320-
321-
if (is_string($output)) {
322-
$output = new ToolOutput(result: $output);
323-
}
324-
325-
$toolResult = new ToolResult(
326-
toolCallId: $toolCall->id,
327-
toolName: $toolCall->name,
328-
args: $toolCall->arguments(),
329-
result: is_array($output->result) ? $output->result : ['result' => $output->result],
330-
artifacts: $output->artifacts,
331-
);
332-
333-
$toolResults[] = $toolResult;
334-
335-
yield new ToolResultEvent(
336-
id: EventID::generate(),
337-
timestamp: time(),
338-
toolResult: $toolResult,
339-
messageId: $this->state->messageId(),
340-
success: true
341-
);
342-
343-
foreach ($toolResult->artifacts as $artifact) {
344-
yield new ArtifactEvent(
345-
id: EventID::generate(),
346-
timestamp: time(),
347-
artifact: $artifact,
348-
toolCallId: $toolCall->id,
349-
toolName: $toolCall->name,
350-
messageId: $this->state->messageId(),
351-
);
352-
}
353-
} catch (Throwable $e) {
354-
$errorResult = new ToolResult(
355-
toolCallId: $toolCall->id,
356-
toolName: $toolCall->name,
357-
args: $toolCall->arguments(),
358-
result: []
359-
);
360-
361-
$toolResults[] = $errorResult;
362-
363-
yield new ToolResultEvent(
364-
id: EventID::generate(),
365-
timestamp: time(),
366-
toolResult: $errorResult,
367-
messageId: $this->state->messageId(),
368-
success: false,
369-
error: $e->getMessage()
370-
);
371-
}
372-
}
312+
yield from $this->callToolsAndYieldEvents($request->tools(), $mappedToolCalls, $this->state->messageId(), $toolResults);
373313

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

0 commit comments

Comments
 (0)