Skip to content

Add AI_Service layer for centralized AI operations#101

Merged
jeffpaul merged 14 commits intoWordPress:developfrom
caseymanos:issue-26-ai-connection-manager
Jan 23, 2026
Merged

Add AI_Service layer for centralized AI operations#101
jeffpaul merged 14 commits intoWordPress:developfrom
caseymanos:issue-26-ai-connection-manager

Conversation

@caseymanos
Copy link
Contributor

@caseymanos caseymanos commented Nov 26, 2025

What?

Closes #26

Implements a centralized AI service layer (AI_Service) that provides a consistent interface for experimental features to communicate with AI providers through the WP AI Client SDK.

Why?

The plugin needs a unified service layer so that experimental features can communicate with AI providers without duplicating logic or directly coupling to the AI_Client SDK. This provides:

  • A single point of configuration for AI provider requests
  • Graceful handling when no provider is configured
  • Hooks and filters for developers/hosts to customize behavior
  • A foundation for future AI-powered features

How?

New files:

  • includes/Services/AI_Service.php - Singleton service class with generate_text(), generate_texts(), create_prompt(), is_available() methods
  • includes/Services/Contracts/AI_Service_Interface.php - Interface contract ensuring consistent implementation
  • tests/Integration/Includes/Services/AI_ServiceTest.php - Integration tests (26 test cases)

Modified files:

  • includes/bootstrap.php - Initializes AI_Service after AI_Client::init()
  • includes/helpers.php - Adds get_ai_service() helper function

Hooks and filters:

Hook Type Description
ai_service_available Filter Override provider availability detection
ai_service_initialized Action Fires when AI service initializes
ai_service_prompt_builder Filter Modify prompt builder before generation

Testing Instructions

  1. Ensure you have an AI provider configured (Settings > AI Credentials)
  2. Run automated tests: npm run test:php
  3. Verify the service via WP-CLI:
npm run wp-env -- run cli wp eval '
$service = WordPress\AI\get_ai_service();
echo "Available: " . ($service->is_available() ? "yes" : "no") . "\n";
if ($service->is_available()) {
    $result = $service->generate_text("Say hello in 3 words");
    echo "Result: " . $result . "\n";
}
'
  1. Test without credentials by temporarily removing them - is_available() should return false
  2. Verify hooks work by adding a filter:
add_filter( 'ai_service_available', '__return_false' );
// Service should now report unavailable

Testing Instructions for Keyboard

Not applicable - this PR adds backend service infrastructure only with no UI changes.

Screenshots or screencast

Not applicable - this PR adds backend service infrastructure only with no UI changes.

Open WordPress Playground Preview
Implements a service layer that provides a consistent interface for
experimental features to communicate with AI providers through the
WP AI Client SDK.

New files:
- includes/Services/AI_Service.php - Singleton service class
- includes/Services/Contracts/AI_Service_Interface.php - Interface contract
- tests/Integration/Includes/Services/AI_ServiceTest.php - Integration tests

Features:
- Centralized AI provider configuration and request handling
- Model preference caching with clear_model_cache() method
- Graceful handling when no provider is configured
- Helper function get_ai_service() for easy access

Hooks and filters:
- ai_service_available: Override provider availability detection
- ai_service_initialized: Action when service initializes
- ai_service_prompt_builder: Filter prompt builder before generation

Closes WordPress#26
@github-actions
Copy link

github-actions bot commented Nov 26, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @iTsphillgood.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: iTsphillgood.

Co-authored-by: caseymanos <tinfl@git.wordpress.org>
Co-authored-by: dkotter <dkotter@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>
Co-authored-by: jeffpaul <jeffpaul@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@codecov
Copy link

codecov bot commented Nov 26, 2025

Codecov Report

❌ Patch coverage is 88.88889% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.91%. Comparing base (1c00879) to head (9a4845d).
⚠️ Report is 35 commits behind head on develop.

Files with missing lines Patch % Lines
...bilities/Excerpt_Generation/Excerpt_Generation.php 0.00% 1 Missing ⚠️
includes/Abilities/Summarization/Summarization.php 0.00% 1 Missing ⚠️
...es/Abilities/Title_Generation/Title_Generation.php 0.00% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             develop     #101      +/-   ##
=============================================
+ Coverage      50.33%   50.91%   +0.57%     
- Complexity       366      375       +9     
=============================================
  Files             26       27       +1     
  Lines           1951     1974      +23     
=============================================
+ Hits             982     1005      +23     
  Misses           969      969              
Flag Coverage Δ
unit 50.91% <88.88%> (+0.57%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
@jeffpaul
Copy link
Member

@caseymanos thanks for the PR! I see you note this closes #26, but looking to double confirm that the various goals in that issue description are all covered by your work here; correct?

Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While overall I agree that a more opinionated AI service makes sense in the context of this plugin, I think it does too many things. Specifically, it obscures some of the flexibility that the underlying WordPress AI Client APIs provide, for no good reason as far as I can tell.

I think we should simplify the implementation to focus on creating a prompt builder with plugin specific defaults applied, and leave the rest to the regular prompt builder APIs.

@iTsphillgood
Copy link

While overall I agree that a more opinionated AI service makes sense in the context of this plugin, I think it does too many things. Specifically, it obscures some of the flexibility that the underlying WordPress AI Client APIs provide, for no good reason as far as I can tell.

I think we should simplify the implementation to focus on creating a prompt builder with plugin specific defaults applied, and leave the rest to the regular prompt builder APIs.

Have you tried Google Jules agent or Gemini 3 on cli?

@jeffpaul jeffpaul changed the base branch from trunk to develop November 26, 2025 22:08
Address @felixarntz review comments:

- Remove AI_Service_Interface (premature abstraction)
- Remove DEFAULT_TEMPERATURE constant (no forced defaults)
- Remove generate_text/generate_texts methods (redundant wrappers)
- Make create_prompt() the centerpiece with optional $options array
- Use existing get_preferred_models() helper instead of duplicating
- Simplify from 311 to 181 lines

The new API follows Felix's suggested pattern:
$text = $ai_service->create_prompt($prompt, $options)->generate_text();
@caseymanos caseymanos force-pushed the issue-26-ai-connection-manager branch from b3bfd72 to 8e0a728 Compare November 27, 2025 09:26
@caseymanos
Copy link
Contributor Author

@felixarntz @jeffpaul
Thanks for the feedback! I made the following improvements:

  1. Removed the interface - Deleted AI_Service_Interface.php
  2. Removed DEFAULT_TEMPERATURE - No forced defaults; consumers specify explicitly or use provider defaults
  3. Made create_prompt() the centerpiece - Removed generate_text() and generate_texts() wrapper methods

Additional improvements:

  • Simplified options handling using the SDK's ModelConfig::fromArray()
  • Options array supports all 11 SDK scalar options with snake_case keys
  • Full SDK API remains available via method chaining after create_prompt()

Updated API

// Simple usage - model preferences auto-applied
$text = get_ai_service()->create_prompt('Summarize this...')->generate_text();

// With options array
$text = get_ai_service()->create_prompt('Translate to French', [
    'system_instruction' => 'You are a translator.',
    'temperature'        => 0.3,
])->generate_text();

// Full SDK access via chaining
$titles = get_ai_service()->create_prompt('Generate titles')
    ->using_candidate_count(5)
    ->generate_texts();

Hooks Available

| Hook                   | Type   | Description                              |
|------------------------|--------|------------------------------------------|
| ai_service_available   | Filter | Override provider availability detection |
| ai_service_initialized | Action | Fires when AI service initializes        |
| ai_preferred_models    | Filter | Customize default model preferences      |

Note: Removed ai_service_prompt_builder filter as it's no longer needed - create_prompt() returns the builder directly so consumers can modify it with the SDK
@jeffpaul jeffpaul requested a review from felixarntz November 28, 2025 15:54
Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@caseymanos Almost LGTM, just one remaining concern.

@felixarntz
Copy link
Member

Related: @dkotter @caseymanos I'm not sure about the get_preferred_models() function, only spotted it now. Didn't want to flag it during the review since it's already in the codebase. Can we rename this to something like get_preferred_models_for_text_generation()? Because that's closer to what that function does, based on the models it currently returns.

Address Felix's review feedback:
- Remove is_available() method from AI_Service (SDK provides
  is_supported_for_*() methods on Prompt_Builder for capability checking)
- Rename get_preferred_models() to get_preferred_models_for_text_generation()
  to clarify the function returns text-generation-specific models
- Rename filter from ai_preferred_models to
  ai_preferred_models_for_text_generation
- Update docstring example to use SDK's is_supported_for_text_generation()
@caseymanos
Copy link
Contributor Author

@felixarntz
Addressed both comments - removed is_available() entirely, which also gets rid of the hardcoded
option string. Updated the docstring example to show using the SDK's
is_supported_for_text_generation() instead.

Also renamed get_preferred_models() to get_preferred_models_for_text_generation() (filter renamed too).

@jeffpaul jeffpaul requested a review from felixarntz December 1, 2025 03:25
felixarntz
felixarntz previously approved these changes Dec 1, 2025
Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@caseymanos Thanks, LGTM!

Note there are some merge conflicts though that need to be resolved.

Address dkotter and felixarntz review feedback:
- Rename create_prompt() to create_textgen_prompt() to clarify
  this method is specifically for text generation
- Update @SInCE tags from 0.1.0 to x.x.x for release process
- Rename hook from ai_service_initialized to
  ai_experiments_service_initialized to use correct prefix
@caseymanos
Copy link
Contributor Author

caseymanos commented Dec 2, 2025

@felixarntz @dkotter

  • Renamed create_prompt() to create_textgen_prompt() to make it clear this is specifically for text
    generation, and gives space for future image/audio prompts
  • Updated all @ since tags to x.x.x for the release process
  • Renamed hook from ai_service_initialized to ai_experiments_service_initialized to use the correct prefix
@jeffpaul jeffpaul modified the milestones: 0.1.1, 0.2.0 Dec 2, 2025
dkotter
dkotter previously approved these changes Dec 2, 2025
@dkotter
Copy link
Collaborator

dkotter commented Jan 13, 2026

@caseymanos Thanks for the work here. We've merged in a few other PRs that used the get_preferred_models function and since this PR changes the name of that, we now have a few linting errors. Do you have time to update those references?

Comment on lines 223 to 224
// Initialize the WP AI Client.
AI_Client::init();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this again, we already run this exact same code a few lines above (currently line 206). Is there a reason to initialize it again here? Or can this be removed?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note I removed that here 9a4845d but let me know if that is wrong

Comment on lines 227 to 228
$ai_service = AI_Service::get_instance();
$ai_service->init();
Copy link
Collaborator

@dkotter dkotter Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess question on this as well looking at it again. It seems the init method we are calling here doesn't actually do anything (beyond just running a hook). Do we need this init method and the loading of this class here? Or can we remove this and anyone that wants to use this class can be done via the get_ai_service helper function?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note I removed these lines and the init method here: 9a4845d. But let me know if that will cause problems

@jeffpaul jeffpaul mentioned this pull request Jan 16, 2026
20 tasks
@jeffpaul jeffpaul modified the milestones: 0.2.0, 0.3.0 Jan 20, 2026
@jeffpaul jeffpaul merged commit 0412047 into WordPress:develop Jan 23, 2026
28 checks passed
@dkotter dkotter modified the milestones: 0.3.0, 0.2.1 Jan 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

5 participants