Skip to content

Commit 80ea175

Browse files
committed
Use AI Services plugin
1 parent 30fb5be commit 80ea175

File tree

2 files changed

+131
-78
lines changed

2 files changed

+131
-78
lines changed

src/AiCommand.php

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,7 @@ public function __invoke( $args, $assoc_args ) {
9898

9999
$client = new MCP\Client( $server );
100100

101-
$result = $client->callGemini( [
102-
[
103-
"role" => "user",
104-
"parts" => [
105-
"text" => $args[0]
106-
]
107-
]
108-
] );
101+
$result = $client->call_ai_service_with_prompt( $args[0] );
109102

110103
WP_CLI::success( $result );
111104
}

src/MCP/Client.php

Lines changed: 130 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
namespace WP_CLI\AiCommand\MCP;
44

55
use Exception;
6+
use Felix_Arntz\AI_Services\Services\API\Enums\AI_Capability;
7+
use Felix_Arntz\AI_Services\Services\API\Enums\Content_Role;
8+
use Felix_Arntz\AI_Services\Services\API\Helpers;
9+
use Felix_Arntz\AI_Services\Services\API\Types\Content;
10+
use Felix_Arntz\AI_Services\Services\API\Types\Parts;
11+
use Felix_Arntz\AI_Services\Services\API\Types\Parts\Function_Call_Part;
12+
use Felix_Arntz\AI_Services\Services\API\Types\Parts\Text_Part;
13+
use Felix_Arntz\AI_Services\Services\API\Types\Tools;
614

715
class Client {
816

@@ -47,94 +55,146 @@ public function read_resource( $uri ) {
4755
return $this->sendRequest( 'resources/read', [ 'uri' => $uri ] );
4856
}
4957

50-
public function callGemini( $contents ) {
58+
public function generate_image( string $prompt ) {
59+
// See https://github.com/felixarntz/ai-services/issues/25.
60+
add_filter(
61+
'map_meta_cap',
62+
static function () {
63+
return [ 'exist' ];
64+
}
65+
);
66+
67+
try {
68+
$service = ai_services()->get_available_service(
69+
[
70+
'capabilities' => [
71+
AI_Capability::IMAGE_GENERATION,
72+
]
73+
]
74+
);
75+
$candidates = $service
76+
->get_model(
77+
[
78+
'feature' => 'image-generation',
79+
'capabilities' => [
80+
AI_Capability::IMAGE_GENERATION,
81+
],
82+
]
83+
)
84+
->generate_image( $prompt );
85+
86+
$image_url = '';
87+
foreach ( $candidates->get( 0 )->get_content()->get_parts() as $part ) {
88+
if ( $part instanceof \Felix_Arntz\AI_Services\Services\API\Types\Parts\Inline_Data_Part ) {
89+
$image_url = $part->get_base64_data(); // Data URL.
90+
break;
91+
}
92+
if ( $part instanceof \Felix_Arntz\AI_Services\Services\API\Types\Parts\File_Data_Part ) {
93+
$image_url = $part->get_file_uri(); // Actual URL. May have limited TTL (often 1 hour).
94+
break;
95+
}
96+
}
97+
98+
// See https://github.com/felixarntz/ai-services/blob/main/docs/Accessing-AI-Services-in-PHP.md for further processing.
99+
100+
return $image_url;
101+
} catch ( Exception $e ) {
102+
\WP_CLI::error( $e->getMessage() );
103+
}
104+
105+
\WP_CLI::error( 'Could not generate image.' );
106+
}
107+
108+
public function call_ai_service_with_prompt( string $prompt ) {
109+
$parts = new Parts();
110+
$parts->add_text_part( $prompt );
111+
$content = new Content( Content_Role::USER, $parts );
112+
113+
return $this->call_ai_service( [ $content ] );
114+
}
115+
116+
private function call_ai_service( $contents ) {
117+
// See https://github.com/felixarntz/ai-services/issues/25.
118+
add_filter(
119+
'map_meta_cap',
120+
static function () {
121+
return [ 'exist' ];
122+
}
123+
);
124+
51125
$capabilities = $this->get_capabilities();
52126

53-
$tools = [];
127+
$function_declarations = [];
54128

55129
foreach ( $capabilities['methods'] ?? [] as $tool ) {
56-
$tools[] = [
130+
$function_declarations[] = [
57131
"name" => $tool['name'],
58132
"description" => $tool['description'] ?? "", // Provide a description
59133
"parameters" => $tool['inputSchema'] ?? [], // Provide the inputSchema
60134
];
61135
}
62136

63-
\WP_CLI::log( 'Calling Gemini...' . json_encode( [
64-
65-
'contents' => $contents,
66-
'tools' => [
67-
'function_declarations' => $tools,
68-
],
69-
] ) );
70-
71-
$GOOGLE_API_KEY = getenv( 'GEMINI_API_KEY' );
72-
73-
$response = \WP_CLI\Utils\http_request(
74-
'POST',
75-
// "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=$GOOGLE_API_KEY",
76-
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=$GOOGLE_API_KEY",
77-
json_encode( [
78-
'contents' => $contents,
79-
'tools' => [
80-
'function_declarations' => $tools,
81-
],
82-
]
83-
),
84-
[
85-
'Content-Type' => 'application/json'
86-
]
87-
);
88-
89-
$data = json_decode( $response->body );
90-
91-
\WP_CLI::log( 'Receiving response...' . json_encode( $data ) );
92-
93137
$new_contents = $contents;
94138

95-
foreach ( $data->candidates[0]->content->parts as $part ) {
96-
// Check for tool calls in Gemini response
97-
if ( isset( $part->functionCall ) ) {
98-
$name = $part->functionCall->name;
99-
$args = (array) $part->functionCall->args;
139+
$tools = new Tools();
140+
$tools->add_function_declarations_tool( $function_declarations );
100141

101-
$functionResult = $this->$name( $args );
102-
103-
\WP_CLI::log( "Calling function $name... Result:" . print_r( $functionResult, true ) );
104-
105-
$new_contents[] = [
106-
'role' => 'model',
107-
'parts' => [
108-
$part
142+
try {
143+
$service = ai_services()->get_available_service(
144+
[
145+
'capabilities' => [
146+
AI_Capability::MULTIMODAL_INPUT,
147+
AI_Capability::TEXT_GENERATION,
148+
AI_Capability::FUNCTION_CALLING,
109149
]
110-
];
111-
$new_contents[] = [
112-
'role' => 'user',
113-
'parts' => [
114-
[
115-
'functionResponse' => [
116-
'name' => $name,
117-
'response' => [
118-
'name' => $name,
119-
'content' => $functionResult,
120-
]
121-
]
122-
]
150+
]
151+
);
152+
153+
\WP_CLI::log( "Making request..." . print_r( $contents, true ) );
154+
155+
$candidates = $service
156+
->get_model(
157+
[
158+
'feature' => 'text-generation',
159+
'tools' => $tools,
160+
'capabilities' => [
161+
AI_Capability::MULTIMODAL_INPUT,
162+
AI_Capability::TEXT_GENERATION,
163+
AI_Capability::FUNCTION_CALLING,
164+
],
123165
]
124-
];
166+
)
167+
->generate_text( $contents );
168+
169+
$text = '';
170+
foreach ( $candidates->get( 0 )->get_content()->get_parts() as $part ) {
171+
if ( $part instanceof Text_Part ) {
172+
if ( $text !== '' ) {
173+
$text .= "\n\n";
174+
}
175+
$text .= $part->get_text();
176+
} elseif ( $part instanceof Function_Call_Part ) {
177+
$function_result = $this->{$part->get_name()}( $part->get_args() );
178+
179+
// Odd limitation of add_function_response_part().
180+
if ( ! is_array( $function_result ) ) {
181+
$function_result = [ $function_result ];
182+
}
183+
184+
$parts = new Parts();
185+
$parts->add_function_response_part( $part->get_id(),$part->get_name(), $function_result );
186+
$content = new Content( Content_Role::USER, $parts );
187+
$new_contents[] = $content;
188+
}
125189
}
126-
}
127190

128-
if ( $new_contents !== $contents ) {
129-
return $this->callGemini( $new_contents );
130-
}
131-
132-
foreach ( $data->candidates[0]->content->parts as $part ) {
133-
if ( isset( $part->text ) ) {
134-
return $part->text;
191+
if ( $new_contents !== $contents ) {
192+
return $this->call_ai_service( $new_contents );
135193
}
136-
}
137194

138-
return 'Unknown!';
195+
return $text;
196+
} catch ( Exception $e ) {
197+
\WP_CLI::error( $e->getMessage() );
198+
}
139199
}
140200
}

0 commit comments

Comments
 (0)