44
55namespace PhpLlm \LlmChain \Bridge \Anthropic ;
66
7+ use PhpLlm \LlmChain \Chain \ToolBox \Metadata ;
78use PhpLlm \LlmChain \Exception \RuntimeException ;
9+ use PhpLlm \LlmChain \Model \Message \AssistantMessage ;
810use PhpLlm \LlmChain \Model \Message \MessageBagInterface ;
11+ use PhpLlm \LlmChain \Model \Message \MessageInterface ;
12+ use PhpLlm \LlmChain \Model \Message \ToolCallMessage ;
913use PhpLlm \LlmChain \Model \Model ;
1014use PhpLlm \LlmChain \Model \Response \ResponseInterface as LlmResponse ;
1115use PhpLlm \LlmChain \Model \Response \StreamResponse ;
1216use PhpLlm \LlmChain \Model \Response \TextResponse ;
17+ use PhpLlm \LlmChain \Model \Response \ToolCall ;
18+ use PhpLlm \LlmChain \Model \Response \ToolCallResponse ;
1319use PhpLlm \LlmChain \Platform \ModelClient ;
1420use PhpLlm \LlmChain \Platform \ResponseConverter ;
1521use Symfony \Component \HttpClient \Chunk \ServerSentEvent ;
@@ -40,13 +46,58 @@ public function request(Model $model, object|array|string $input, array $options
4046 {
4147 Assert::isInstanceOf ($ input , MessageBagInterface::class);
4248
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+
4364 $ system = $ input ->getSystemMessage ();
4465 $ body = array_merge ($ options , [
4566 'model ' => $ model ->getVersion (),
4667 'system ' => $ system ->content ,
47- 'messages ' => $ input ->withoutSystemMessage (),
68+ 'messages ' => $ input ->withoutSystemMessage ()-> jsonSerialize () ,
4869 ]);
4970
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+
50101 return $ this ->httpClient ->request ('POST ' , 'https://api.anthropic.com/v1/messages ' , [
51102 'headers ' => [
52103 'x-api-key ' => $ this ->apiKey ,
@@ -72,6 +123,16 @@ public function convert(ResponseInterface $response, array $options = []): LlmRe
72123 throw new RuntimeException ('Response content does not contain any text ' );
73124 }
74125
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+
75136 return new TextResponse ($ data ['content ' ][0 ]['text ' ]);
76137 }
77138
0 commit comments