Skip to content

Force a model to be passed when generating embeddings instead of relying on the model resolver - #274

Open
dkotter wants to merge 8 commits into
WordPress:trunkfrom
dkotter:update/embedding-model-resolver
Open

Force a model to be passed when generating embeddings instead of relying on the model resolver#274
dkotter wants to merge 8 commits into
WordPress:trunkfrom
dkotter:update/embedding-model-resolver

Conversation

@dkotter

@dkotter dkotter commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

What?

Instead of relying on the ModelResolver to determine which model should be used when running the EmbeddingBuilder (which is what the PromptBuilder does), require a specific model to be passed in and if not, return an error.

Why?

Embedding vectors created by one model aren't compatible with vectors created by another model. For this reason it's important that a specific model is always provided instead of relying on a ModelResolver to determine this, which has a high likelihood of choosing a different model at various points.

The original version of embeddings (added in #244) closely followed what we already do in the PromptBuilder, but we realized that we should deviate slightly to force a model be passed. This PR makes that update and now an error will be returned if someone tries to use the EmbeddingBuilder without passing in a specific model.

How?

  • Modify our various generate methods to require a model be passed in
  • Extract some functionality out of the ModelResolutionTrait into a new ModelConfigurationTrait and use that in our EmbeddingBuilder
  • Ensure the EmbeddingBuilder sets the model properly and validates that the model is available and will work for the request
  • Add a new getUnmetRequirements method that will tell us all of the requirements that aren't met, allowing us to provide more detailed error messages

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Opus 5
Used for: Evaluating the existing code, iterating on a plan to make the above changes and then executing that plan. All code was reviewed and tested by me

Testing Instructions

Hard to test this PR on it's own as it requires an AI Provider that supports embeddings. Currently we have upstream PRs that add this support in but those haven't been released yet. Easiest way to test is the following:

  1. Remove the old packages. These have been renamed (these are fixed in Update the AI Provider plugins to their latest versions #275)
composer remove --dev --no-update \
  wordpress/anthropic-ai-provider \
  wordpress/google-ai-provider \
  wordpress/openai-ai-provider
  1. Configure the OpenAI fork
composer config repositories.ai-provider-for-openai vcs \
  https://github.com/chubes4/ai-provider-for-openai
  1. Install the providers from the right spot (specific branch or release)
composer require --dev \
  "wordpress/ai-provider-for-anthropic:^1.0" \
  "wordpress/ai-provider-for-google:dev-feature/embeddings" \
  "wordpress/ai-provider-for-openai:dev-feature/issue-32-openai-embeddings"
  1. Add a .env file and on one line, add GOOGLE_API_KEY=YOUR KEY HERE and on the next line add OPENAI_API_KEY=YOUR KEY HERE
  2. Run the integration test suite: composer test:integration (Note there are a couple errors with function calling tests but those are existing issues, not related to this PR)

After the above, you can also test directly using our cli.php file:

OPENAI_API_KEY=123456 php cli.php 'Your text here' --providerId=openai --modelId=text-embedding-3-small --outputFormat=embedding-json

You should see output on the command line that shows the embedding result.

Changelog Entry

Changed - Require a specific model be passed when generating embeddings instead of relying on a model resolver. Note this is a breaking change for anyone that has started to use embedding functionality and will require updates be made.

…ments are unmet so we can provide a more specific error message to a user
…ModelResolutionTrait to better support changes we need in the EmbeddingBuilder
…that will ensure the model provided is valid and will work for the request. Update our helpers in the AiClient to require a model be passed
@dkotter dkotter added this to the 1.4.0 milestone Aug 13, 2026
@dkotter dkotter self-assigned this Aug 13, 2026
@dkotter
dkotter requested a review from JasonTheAdams August 13, 2026 17:06
@github-actions

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: dkotter <dkotter@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 Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.34177% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.44%. Comparing base (a31b0ec) to head (604a714).

Files with missing lines Patch % Lines
src/Builders/EmbeddingBuilder.php 88.13% 14 Missing ⚠️
src/Builders/Traits/ModelConfigurationTrait.php 0.00% 6 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##              trunk     #274      +/-   ##
============================================
- Coverage     86.49%   86.44%   -0.06%     
- Complexity     1327     1373      +46     
============================================
  Files            68       69       +1     
  Lines          4295     4419     +124     
============================================
+ Hits           3715     3820     +105     
- Misses          580      599      +19     
Flag Coverage Δ
unit 86.44% <87.34%> (-0.06%) ⬇️

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.

@jeffpaul jeffpaul modified the milestones: 1.4.0, 1.5.0 Aug 13, 2026
…pecified, not if an invalid model was provided. This matches what the README claims, where someone can run isSupported to see if a model is supported without worrying about catching exceptions
…rate this out from prepareModel so we only touch model config prior to making a request, not verifying things

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the embedding-generation surface so callers must explicitly specify which model is used (instead of relying on resolver-based auto-selection), and enhances model requirement validation to produce more actionable errors. This aligns with embeddings’ constraint that vectors are only comparable within the same model.

Changes:

  • Require an explicit embedding model (via usingModel() or usingProviderModel()) and validate provider configuration + model capabilities/options before requesting embeddings.
  • Add ModelRequirements::getUnmetRequirements() to report all unsupported capabilities/options, enabling more detailed error messages.
  • Update unit/integration tests, CLI behavior, and documentation to reflect the explicit-model requirement and new failure modes.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit/Providers/Models/DTO/ModelRequirementsTest.php Adds coverage for getUnmetRequirements() and additional areMetBy() edge cases.
tests/unit/Builders/EmbeddingBuilderTest.php Updates embedding builder tests for explicit model requirement, provider-config checks, and option validation behavior.
tests/unit/AiClientTest.php Updates traditional embedding API tests for the new required-model signature and tuple support.
tests/traits/MockModelCreationTrait.php Enhances embedding model metadata helper to declare realistic supported options by default.
tests/integration/OpenAi/EmbeddingGenerationIntegrationTest.php Updates OpenAI integration tests to always specify an embedding model and adds new negative tests.
tests/integration/Google/EmbeddingGenerationIntegrationTest.php Adds Google embedding integration tests with explicit model selection and negative cases.
src/Providers/Models/DTO/ModelRequirements.php Implements getUnmetRequirements() and refactors areMetBy() to use it.
src/Builders/Traits/ModelResolutionTrait.php Refactors trait to focus on model selection and delegate config handling to ModelConfigurationTrait.
src/Builders/Traits/ModelConfigurationTrait.php Introduces shared model-config accumulation/merging for builders.
src/Builders/EmbeddingBuilder.php Reworks builder to require and verify an explicitly named model (no resolver-based discovery).
src/AiClient.php Updates traditional embedding APIs to require a model and adds helper to configure an embedding builder.
README.md Updates embedding docs to require explicit model and explains why; adds discovery example.
docs/ARCHITECTURE.md Updates builder architecture explanation and embedding examples to reflect explicit model verification approach.
cli.php Requires --providerId + --modelId for embedding outputs and updates builder setup accordingly.
Suppressed comments (2)

src/AiClient.php:470

  • For consistency with generateEmbeddingResult() accepting a legacy 3rd-arg registry, generateEmbedding() should also accept the 3-argument form (input, model, registry) without a TypeError.
    public static function generateEmbedding(
        $input,
        $model,
        ?ModelConfig $modelConfig = null,
        ?ProviderRegistry $registry = null
    ): Embedding {
        return self::generateEmbeddingResult($input, $model, $modelConfig, $registry)->getEmbedding();

src/AiClient.php:496

  • generateEmbeddings() has the same avoidable TypeError risk for existing calls that pass a ProviderRegistry as the 3rd argument. If generateEmbeddingResult() supports the legacy argument ordering, this method should too.
    public static function generateEmbeddings(
        array $inputs,
        $model,
        ?ModelConfig $modelConfig = null,
        ?ProviderRegistry $registry = null
    ): array {
        return self::getConfiguredEmbeddingBuilder($inputs, $model, $modelConfig, $registry)
            ->generateEmbeddings();

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/AiClient.php
Comment on lines 438 to 446
public static function generateEmbeddingResult(
$input,
$modelOrConfig = null,
$model,
?ModelConfig $modelConfig = null,
?ProviderRegistry $registry = null
): EmbeddingResult {
self::validateModelOrConfigParameter($modelOrConfig);
return self::applyModelOrConfig(self::input($input, $registry), $modelOrConfig)
return self::getConfiguredEmbeddingBuilder($input, $model, $modelConfig, $registry)
->generateEmbeddingResult();
}

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.

Yes, this is intentionally a breaking change

Comment thread docs/ARCHITECTURE.md
'PHP powers a large part of the web.',
'WordPress makes publishing accessible.',
])
->usingModel(GoogleProvider::model('gemini-embedding-001'))
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.

3 participants