4
4
5
5
namespace PhpLlm \LlmChain \Bridge \Anthropic ;
6
6
7
+ use PhpLlm \LlmChain \Chain \ToolBox \Metadata ;
7
8
use PhpLlm \LlmChain \Exception \RuntimeException ;
9
+ use PhpLlm \LlmChain \Model \Message \AssistantMessage ;
8
10
use PhpLlm \LlmChain \Model \Message \MessageBagInterface ;
11
+ use PhpLlm \LlmChain \Model \Message \MessageInterface ;
12
+ use PhpLlm \LlmChain \Model \Message \ToolCallMessage ;
9
13
use PhpLlm \LlmChain \Model \Model ;
10
14
use PhpLlm \LlmChain \Model \Response \ResponseInterface as LlmResponse ;
11
15
use PhpLlm \LlmChain \Model \Response \StreamResponse ;
12
16
use PhpLlm \LlmChain \Model \Response \TextResponse ;
17
+ use PhpLlm \LlmChain \Model \Response \ToolCall ;
18
+ use PhpLlm \LlmChain \Model \Response \ToolCallResponse ;
13
19
use PhpLlm \LlmChain \Platform \ModelClient ;
14
20
use PhpLlm \LlmChain \Platform \ResponseConverter ;
15
21
use Symfony \Component \HttpClient \Chunk \ServerSentEvent ;
@@ -40,13 +46,58 @@ public function request(Model $model, object|array|string $input, array $options
40
46
{
41
47
Assert::isInstanceOf ($ input , MessageBagInterface::class);
42
48
49
+ if (isset ($ options ['tools ' ])) {
50
+ $ tools = $ options ['tools ' ];
51
+ $ options ['tools ' ] = [];
52
+ /** @var Metadata $tool */
53
+ foreach ($ tools as $ tool ) {
54
+ $ toolDefinition = [
55
+ 'name ' => $ tool ->name ,
56
+ 'description ' => $ tool ->description ,
57
+ 'input_schema ' => $ tool ->parameters ?? ['type ' => 'object ' ],
58
+ ];
59
+ $ options ['tools ' ][] = $ toolDefinition ;
60
+ }
61
+ $ options ['tool_choice ' ] = ['type ' => 'auto ' ];
62
+ }
63
+
43
64
$ system = $ input ->getSystemMessage ();
44
65
$ body = array_merge ($ options , [
45
66
'model ' => $ model ->getVersion (),
46
67
'system ' => $ system ->content ,
47
- 'messages ' => $ input ->withoutSystemMessage (),
68
+ 'messages ' => $ input ->withoutSystemMessage ()-> jsonSerialize () ,
48
69
]);
49
70
71
+ $ body ['messages ' ] = array_map (static function (MessageInterface $ message ) {
72
+ if ($ message instanceof ToolCallMessage) {
73
+ return [
74
+ 'role ' => 'user ' ,
75
+ 'content ' => [
76
+ [
77
+ 'type ' => 'tool_result ' ,
78
+ 'tool_use_id ' => $ message ->toolCall ->id ,
79
+ 'content ' => $ message ->content ,
80
+ ],
81
+ ],
82
+ ];
83
+ }
84
+ if ($ message instanceof AssistantMessage && $ message ->hasToolCalls ()) {
85
+ return [
86
+ 'role ' => 'assistant ' ,
87
+ 'content ' => array_map (static function (ToolCall $ toolCall ) {
88
+ return [
89
+ 'type ' => 'tool_use ' ,
90
+ 'id ' => $ toolCall ->id ,
91
+ 'name ' => $ toolCall ->name ,
92
+ 'input ' => empty ($ toolCall ->arguments ) ? new \stdClass () : $ toolCall ->arguments ,
93
+ ];
94
+ }, $ message ->toolCalls ),
95
+ ];
96
+ }
97
+
98
+ return $ message ;
99
+ }, $ body ['messages ' ]);
100
+
50
101
return $ this ->httpClient ->request ('POST ' , 'https://api.anthropic.com/v1/messages ' , [
51
102
'headers ' => [
52
103
'x-api-key ' => $ this ->apiKey ,
@@ -72,6 +123,16 @@ public function convert(ResponseInterface $response, array $options = []): LlmRe
72
123
throw new RuntimeException ('Response content does not contain any text ' );
73
124
}
74
125
126
+ $ toolCalls = [];
127
+ foreach ($ data ['content ' ] as $ content ) {
128
+ if ('tool_use ' === $ content ['type ' ]) {
129
+ $ toolCalls [] = new ToolCall ($ content ['id ' ], $ content ['name ' ], $ content ['input ' ]);
130
+ }
131
+ }
132
+ if (!empty ($ toolCalls )) {
133
+ return new ToolCallResponse (...$ toolCalls );
134
+ }
135
+
75
136
return new TextResponse ($ data ['content ' ][0 ]['text ' ]);
76
137
}
77
138
0 commit comments