Skip to content

Add embedding generation APIs - #244

Merged
JasonTheAdams merged 28 commits into
WordPress:trunkfrom
chubes4:issue/242-embedding-generation
Jul 15, 2026
Merged

Add embedding generation APIs#244
JasonTheAdams merged 28 commits into
WordPress:trunkfrom
chubes4:issue/242-embedding-generation

Conversation

@chubes4

@chubes4 chubes4 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds provider-agnostic embedding contracts, immutable result DTOs, model options, and embedding-specific lifecycle events.
  • Introduces a dedicated EmbeddingBuilder and shares model/provider selection with PromptBuilder through a composed ModelResolver.
  • Keeps embedding inputs independent of conversational messages with a direct list<string> provider contract.
  • Supports single and batch embedding generation through fluent and traditional APIs, with one provider call per batch.

Fixes #242.

Testing

  1. Run composer phpcs.
  2. Run composer phpstan.
  3. Run php -d error_reporting='E_ALL & ~E_DEPRECATED' vendor/bin/phpunit --testsuite unit.
  4. Configure .env with OPENAI_API_KEY and install an OpenAI provider branch implementing the embedding contract.
  5. Run composer test:integration -- --filter EmbeddingGenerationIntegrationTest.
  6. Confirm single input returns one embedding, batch input preserves result order and cardinality, and requested dimensions are honored.

Local verification against current trunk: 1,132 tests and 4,184 assertions passed. A live OpenAI cross-package check also verified one-input and two-input requests at 256 dimensions.

AI assistance

  • AI assistance: Yes
  • Tool(s): OpenCode (OpenAI GPT-5.6 Sol and GPT-5.6 Terra)
  • Used for: Implemented and reviewed the builder/model-resolution architecture, provider contract, tests, documentation, and integration verification; Chris directed the architecture and remains responsible for the change.

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

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.

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

Co-authored-by: chubes4 <extrachill@git.wordpress.org>
Co-authored-by: dkotter <dkotter@git.wordpress.org>
Co-authored-by: JasonTheAdams <jason_the_adams@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

codecov Bot commented Jun 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.94949% with 124 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.49%. Comparing base (052a468) to head (66fa4d7).

Files with missing lines Patch % Lines
src/Results/DTO/EmbeddingResult.php 63.10% 38 Missing ⚠️
src/Providers/ModelResolver.php 83.70% 22 Missing ⚠️
src/Providers/Models/DTO/ModelRequirements.php 38.23% 21 Missing ⚠️
src/Events/AfterGenerateEmbeddingEvent.php 0.00% 19 Missing ⚠️
src/Events/BeforeGenerateEmbeddingEvent.php 0.00% 15 Missing ⚠️
src/Results/DTO/Embedding.php 80.00% 8 Missing ⚠️
src/Providers/Models/DTO/ModelConfig.php 93.33% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##              trunk     #244      +/-   ##
============================================
- Coverage     88.16%   86.49%   -1.67%     
- Complexity     1217     1327     +110     
============================================
  Files            61       68       +7     
  Lines          3945     4295     +350     
============================================
+ Hits           3478     3715     +237     
- Misses          467      580     +113     
Flag Coverage Δ
unit 86.49% <74.94%> (-1.67%) ⬇️

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

☔ View full report in Codecov by Harness.
📢 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.

@dkotter

dkotter commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

I know we already have #132 open to add embedding support and while that PR is fairly old now, it has already gone through an initial review. I've not compared the code there to here but curious if the approach taken here is different than that and if so, what the benefits are here as compared to that? Ideally we proceed with a single approach to solve the same problem to not overtax contributors here.

@chubes4

chubes4 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for pointing that out. I honestly missed #132 before opening this, and there's definitely overlap. My apologies for the duplicated effort.

The main distinction I see is that this PR is provider-agnostic, while #132 includes OpenAI-specific implementation that I think belongs in the OpenAI provider package, not php-ai-client.

Happy to close this if maintainers want #132 to remain canonical, or take ownership here if this narrower direction makes more sense.

@dkotter

dkotter commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Happy to close this if maintainers want #132 to remain canonical, or take ownership here if this narrower direction makes more sense.

I'd defer to @JasonTheAdams and/or @felixarntz on that, I think ideal to have a single PR we're working from in order to not duplicate effort but I also know that original PR has sat for months and likely needs work to get it across the finish line so may be worth closing that out and proceeding here instead.

@JasonTheAdams
JasonTheAdams self-requested a review June 10, 2026 04:13

@JasonTheAdams JasonTheAdams left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is great! Thanks for the PR, @chubes4! One key suggestion:

Let's introduce an Embedding Value Object that's iterable and immutable, takes a list<int|float> of values and an integer for dimensions. This will contain all the validations (cleaning up EmbeddingResult and make it a first-class type.

@chubes4

chubes4 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

@JasonTheAdams Pushed some updates with the requested changes. Let me know if you'd like to see anything else, and thanks again!

@JasonTheAdams
JasonTheAdams self-requested a review June 11, 2026 04:46
@dkotter dkotter added this to the 1.4.0 milestone Jul 1, 2026

@dkotter dkotter left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Left a few minor comments but overall this is looking good to me.

A couple other things that ideally we see added here:

  1. We have a cli.php script that is useful for quickly testing things. That currently doesn't support embeddings so would be nice to update that to make testing this PR easier
  2. We should ensure our documentation is updated with this new functionality, in particular the README.md

Comment thread src/Builders/PromptBuilder.php Outdated
Comment thread src/Builders/PromptBuilder.php Outdated
Comment thread src/Results/DTO/Embedding.php
Comment thread src/Results/DTO/EmbeddingResult.php
Comment thread src/Results/DTO/EmbeddingResult.php
Comment thread src/Results/DTO/EmbeddingResult.php
@chubes4

chubes4 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Shipped an update to address the latest review! Thank you

@jeffpaul
jeffpaul requested review from dkotter and felixarntz July 2, 2026 01:23
Comment thread src/Events/AfterGenerateResultEvent.php Outdated
ModelInterface $model,
?CapabilityEnum $capability,
GenerativeAiResult $result
ResultInterface $result

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

While I understand why we're switching from GenerativeAiResult to the more generic ResultInterface in this file, this does open up backward incompatibilities. I'd be inclined to be okay with that if this was just a stand-alone package but because the goal is to merge this back into WordPress core, not sure we can proceed with this approach.

As an example, if someone is using this after event hook and they run some code like this:

$event->getResult()->getCandidates()

This will throw a fatal error if the result is an EmbeddingResult. Consumers would instead need to run a check like this first:

$result = $event->getResult();
if ($result instanceof GenerativeAiResult) {
    // generative handling
} elseif ($result instanceof EmbeddingResult) {
    // embedding handling
}

I think ideally we need to figure out a way to handle the difference between a GenerativeAiResult and EmbeddingResult ourselves (if possible) to avoid that breakage.

Could potentially introduce new getGenerativeResult and getEmbeddingResult methods and keep getResult as is. Or maybe there's other, better options here

dkotter
dkotter previously requested changes Jul 6, 2026
Comment thread src/Builders/PromptBuilder.php Outdated
Comment thread src/Builders/PromptBuilder.php Outdated
Comment thread src/Builders/PromptBuilder.php Outdated
Comment thread src/Builders/PromptBuilder.php Outdated
Comment thread src/Builders/PromptBuilder.php Outdated
@chubes4

chubes4 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Thanks Darrin. I pushed updates to address the latest review feedback:

  • Restored the existing generative result event contract so AfterGenerateResultEvent::getResult() remains GenerativeAiResult.
  • Added embedding-specific before/after events instead of routing embeddings through the generative result event.
  • Fixed batch embedding model selection to derive requirements from the provided batch prompts.
  • Kept batch embeddings as a single model/provider call.
  • Removed the generic encodingFormat API for now.

Also verified the updated client/provider branch pair in WP Codebox with a live OpenAI embeddings call.

@jeffpaul
jeffpaul requested a review from dkotter July 7, 2026 14:07
Comment thread src/Builders/PromptBuilder.php Outdated
);
}

$this->dispatchEvent(new BeforeGenerateEmbeddingEvent($promptMessages, $model, $capability));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Currently the WP AI Client will fire do_action calls for events (see https://github.com/WordPress/wordpress-develop/blob/52e3c3013a9635b45d66c4888f995ade320bc49e/src/wp-includes/ai-client/adapters/class-wp-ai-client-event-dispatcher.php#L56). Right now that means we have two events, wp_ai_client_before_generate_result and wp_ai_client_after_generate_result.

With these new event classes, I believe that will add two new hooks, wp_ai_client_before_generate_embedding and wp_ai_client_after_generate_embedding. This does mean anyone that uses those existing hooks will not capture these new events. Is that something to worry about here? Similar to my last comment about backwards incompatibility, technically this works and won't cause errors but also is a new thing that people will need to be aware of.

Is there anyway to continue to use the existing event system and make it work for both generative AI results and embedding results? Or is there a way to use the generative AI results for embeddings instead of introducing a new DTO specific to embedding results? Or some other middle ground approach?

And maybe we're fine with these changes and the implications, just want to ensure we're thinking through the various ways this will impact any consumers of these events

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

And just to be clear, opening this as something to discuss, not necessarily something that needs changed. Just want to ensure we're okay with introducing new events

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I will have to look into it later this week. I'm about to be off grid camping for a few days. Get back to you asap.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thinking about this a bit more, I've landed on the approach in this PR being the right one, where we introduce new embedding only events instead of trying to force embeddings to use the existing event classes (especially since those are built around GenerativeAiResult).

Not needed for this PR but I do think there's a likely better future where we introduce a shared event interface that the existing events (and events in the future) can all use. A subscriber could then listen for all events there if desired.

In addition, I think a catch-all hook upstream in WordPress would also be useful, so someone wanting to track all events could use that instead of having to keep track of any new event hooks that may get added in the future.

Neither of those are blockers for this PR and I'd recommend those as a separate PR in this repo and one PR in the WordPress-develop repo.

@JasonTheAdams JasonTheAdams left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, @dkotter! I left a couple more comments.

Comment thread src/Results/DTO/Embedding.php Outdated
Comment thread src/Results/DTO/Embedding.php Outdated
Comment thread src/Results/DTO/Embedding.php Outdated
dkotter added 2 commits July 10, 2026 13:41
…ew more places. Remove validation now as we force the input to be an EmbeddingList which only allows arrays with ints or floats
dkotter and others added 10 commits July 10, 2026 16:06
…st and have it iterate through all values. Keep the exception throwing in the constructor to match the others
…mptBuilder into this new class. Goal is to allow the creation of other Builder classes that can use the same model resolver functionality
…tionality into that and out of PromptBuilder
EmbeddingBuilder. Embedding inputs are now MessageParts (strings, Files,
or parts) rather than Message conversations, matching how providers treat
embeddings as a separate APIinor language tweaks
Resolve embeddings around modality-aware MessagePart inputs and a shared provider-level model resolver.
@chubes4

chubes4 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Reconciliation update: this branch now merges Darrin Kotter’s issue/242-embedding-generation-v2 history into the canonical implementation, preserving all four commits and authorship.

The combined API keeps the dedicated EmbeddingBuilder and shared model resolver while adopting modality-aware MessagePart inputs, AiClient::embed() single/list ergonomics, withInput() / withInputs(), and ModelRequirements::fromEmbeddingData(). Embedding inputs remain independent items and providers receive one ordered batch.

Verification on the reconciled head:

  • PHPCS passed
  • PHPStan passed
  • Full unit suite passed: 1,163 tests / 4,259 assertions
  • OpenAI provider integration passed with one and two text inputs at 256 dimensions

The dependent provider update is in WordPress/ai-provider-for-openai#34.

@JasonTheAdams JasonTheAdams left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Alright! We're close! Just a few small suggestions.

Comment thread src/AiClient.php Outdated
Comment thread src/Builders/PromptBuilder.php

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm really happy to see this massive class get trimmed down quite a bit. 😄

Comment thread src/Builders/EmbeddingBuilder.php Outdated
@jeffpaul

Copy link
Copy Markdown
Member

@JasonTheAdams I don't know that anyone else is still around to resolve those final items and merge, but If you're able to do that then I can try and see about getting a release out later this evening (after which we'd still need someone to bump the library in WP core before too long into tomorrow). All that to say, maybe things are too late now?

@chubes4

chubes4 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

It's not too late for me @jeffpaul @JasonTheAdams let's cook this PR. I just pushed an update.

@JasonTheAdams JasonTheAdams left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ran the integration tests locally to confirm this is working against WordPress/ai-provider-for-openai#34, and it worked!

Thanks @chubes4 and @dkotter!

@JasonTheAdams
JasonTheAdams dismissed dkotter’s stale review July 15, 2026 01:27

Darin worked on this directly after requesting changes. All requests have since been met or are no longer relevant.

@JasonTheAdams
JasonTheAdams merged commit 593a04c into WordPress:trunk Jul 15, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement embedding generation contracts and client APIs

4 participants