-
Notifications
You must be signed in to change notification settings - Fork 30
Add AI_Service layer for centralized AI operations #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
caseymanos
wants to merge
8
commits into
WordPress:develop
Choose a base branch
from
caseymanos:issue-26-ai-connection-manager
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+400
−24
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
80879bb
Add AI_Service layer for centralized AI operations
caseymanos 8e0a728
Refactor AI_Service per review feedback
caseymanos edc69eb
refactor to use ModelConfig for options array
caseymanos b1f12bc
Remove is_available() and rename get_preferred_models()
caseymanos 7157270
Rename create_prompt to create_textgen_prompt and update conventions
caseymanos 34ab73a
Merge origin/trunk into issue-26-ai-connection-manager
caseymanos 45f813a
Merge origin/develop into issue-26-ai-connection-manager
caseymanos acdbfaf
Merge branch 'develop' into issue-26-ai-connection-manager
caseymanos File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,201 @@ | ||
| <?php | ||
| /** | ||
| * AI Service implementation. | ||
| * | ||
| * Provides a centralized service layer for AI operations. | ||
| * | ||
| * @package WordPress\AI\Services | ||
| */ | ||
|
|
||
| declare( strict_types=1 ); | ||
|
|
||
| namespace WordPress\AI\Services; | ||
|
|
||
| use WordPress\AI_Client\AI_Client; | ||
| use WordPress\AI_Client\Builders\Prompt_Builder_With_WP_Error; | ||
| use WordPress\AiClient\Providers\Models\DTO\ModelConfig; | ||
|
|
||
| use function WordPress\AI\get_preferred_models_for_text_generation; | ||
|
|
||
| /** | ||
| * AI Service class. | ||
| * | ||
| * Manages AI provider configuration and provides a consistent interface | ||
| * for experimental features to communicate with AI providers. | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| class AI_Service { | ||
|
|
||
| /** | ||
| * Singleton instance. | ||
| * | ||
| * @since x.x.x | ||
| * | ||
| * @var \WordPress\AI\Services\AI_Service|null | ||
| */ | ||
| private static ?self $instance = null; | ||
|
|
||
| /** | ||
| * Whether the service has been initialized. | ||
| * | ||
| * @since x.x.x | ||
| * | ||
| * @var bool | ||
| */ | ||
| private bool $initialized = false; | ||
|
|
||
| /** | ||
| * Option key mapping from WordPress snake_case to SDK camelCase. | ||
| * | ||
| * @since x.x.x | ||
| * | ||
| * @var array<string, string> | ||
| */ | ||
| private static array $option_key_map = array( | ||
| 'system_instruction' => 'systemInstruction', | ||
| 'candidate_count' => 'candidateCount', | ||
| 'max_tokens' => 'maxTokens', | ||
| 'temperature' => 'temperature', | ||
| 'top_p' => 'topP', | ||
| 'top_k' => 'topK', | ||
| 'stop_sequences' => 'stopSequences', | ||
| 'presence_penalty' => 'presencePenalty', | ||
| 'frequency_penalty' => 'frequencyPenalty', | ||
| 'logprobs' => 'logprobs', | ||
| 'top_logprobs' => 'topLogprobs', | ||
| ); | ||
|
|
||
| /** | ||
| * Gets the singleton instance. | ||
| * | ||
| * @since x.x.x | ||
| * | ||
| * @return \WordPress\AI\Services\AI_Service The singleton instance. | ||
| */ | ||
| public static function get_instance(): self { | ||
| if ( null === self::$instance ) { | ||
| self::$instance = new self(); | ||
| } | ||
| return self::$instance; | ||
| } | ||
|
|
||
| /** | ||
| * Private constructor to enforce singleton pattern. | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| private function __construct() {} | ||
|
|
||
| /** | ||
| * Initializes the AI service. | ||
| * | ||
| * This method should be called after AI_Client::init() on the WordPress 'init' hook. | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| public function init(): void { | ||
| if ( $this->initialized ) { | ||
| return; | ||
| } | ||
|
|
||
| $this->initialized = true; | ||
|
|
||
| /** | ||
| * Fires when the AI service is initialized. | ||
| * | ||
| * @since x.x.x | ||
| * | ||
| * @param \WordPress\AI\Services\AI_Service $service The AI service instance. | ||
| */ | ||
| do_action( 'ai_experiments_service_initialized', $this ); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a text generation prompt builder with default configuration applied. | ||
| * | ||
| * This is the primary method for text generation with AI providers. It returns | ||
| * a configured prompt builder that consumers can use with the full SDK API. | ||
| * | ||
| * Example usage: | ||
| * ```php | ||
| * $service = AI_Service::get_instance(); | ||
| * | ||
| * // Simple usage | ||
| * $text = $service->create_textgen_prompt( 'Summarize this text' )->generate_text(); | ||
| * | ||
| * // With options | ||
| * $text = $service->create_textgen_prompt( 'Translate to French', array( | ||
| * 'system_instruction' => 'You are a translator.', | ||
| * 'temperature' => 0.3, | ||
| * 'max_tokens' => 500, | ||
| * ) )->generate_text(); | ||
| * | ||
| * // Generate multiple candidates | ||
| * $titles = $service->create_textgen_prompt( 'Generate titles', array( | ||
| * 'candidate_count' => 5, | ||
| * 'temperature' => 0.8, | ||
| * ) )->generate_texts(); | ||
| * ``` | ||
| * | ||
| * @since x.x.x | ||
| * | ||
| * @param string|null $prompt Optional. Initial prompt content. | ||
| * @param array<string, mixed> $options Optional. Configuration options. { | ||
| * @type string $system_instruction System instruction for the AI. | ||
| * @type float $temperature Temperature for generation (0.0-2.0). | ||
| * @type int $max_tokens Maximum tokens to generate. | ||
| * @type float $top_p Top-p (nucleus) sampling value. | ||
| * @type int $top_k Top-k sampling value. | ||
| * @type int $candidate_count Number of candidates to generate. | ||
| * @type float $presence_penalty Presence penalty for generation. | ||
| * @type float $frequency_penalty Frequency penalty for generation. | ||
| * @type list<string> $stop_sequences Stop sequences for generation. | ||
| * @type bool $logprobs Whether to return log probabilities. | ||
| * @type int $top_logprobs Top log probabilities to return. | ||
| * } | ||
| * @return \WordPress\AI_Client\Builders\Prompt_Builder_With_WP_Error The prompt builder instance. | ||
| */ | ||
| public function create_textgen_prompt( ?string $prompt = null, array $options = array() ): Prompt_Builder_With_WP_Error { | ||
| $builder = AI_Client::prompt_with_wp_error( $prompt ); | ||
|
|
||
| // Apply default model preferences. | ||
| $models = get_preferred_models_for_text_generation(); | ||
| if ( ! empty( $models ) ) { | ||
| $builder = $builder->using_model_preference( ...$models ); | ||
| } | ||
|
|
||
| // Apply options via ModelConfig if any are provided. | ||
| if ( ! empty( $options ) ) { | ||
| $config_array = $this->map_options_to_config( $options ); | ||
| if ( ! empty( $config_array ) ) { | ||
| $config = ModelConfig::fromArray( $config_array ); | ||
| $builder = $builder->using_model_config( $config ); | ||
| } | ||
| } | ||
|
|
||
| return $builder; | ||
| } | ||
|
|
||
| /** | ||
| * Maps WordPress snake_case options to SDK camelCase config array. | ||
| * | ||
| * @since x.x.x | ||
| * | ||
| * @param array<string, mixed> $options The options array with snake_case keys. | ||
| * @return array<string, mixed> The mapped config array with camelCase keys. | ||
| */ | ||
| private function map_options_to_config( array $options ): array { | ||
| $config = array(); | ||
|
|
||
| foreach ( self::$option_key_map as $wp_key => $sdk_key ) { | ||
| if ( ! array_key_exists( $wp_key, $options ) ) { | ||
| continue; | ||
| } | ||
|
|
||
| $config[ $sdk_key ] = $options[ $wp_key ]; | ||
| } | ||
|
|
||
| return $config; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I initially had something similar in place when building out the Title Generation Experiment before getting feedback that it made things over-abstracted (see #67 (review)). So may be worth revisiting that discussion to ensure we're all on the same page cc/ @JasonTheAdams
That said, overall I'm fine with this approach but seems we just add the
AI_Serviceclass in this PR but we're not actually using it anywhere. Do we want to update how the Title Generation Experiment is working to take advantage of this new approach?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wary of overabstraction here too, which is why the PR has been notably reduced in scope from what it was originally.
I think the way it currently is is fine. Although it mostly feels like syntactic sugar and a slightly alternative way to use the fluent API from the WP AI Client prompt builder.