Skip to content

Conversation

@dmontagu
Copy link
Contributor

@dmontagu dmontagu commented Oct 24, 2025

Started this in collaboration with @DouweM, I'd like to ensure consensus on the API design before adding the remaining-providers/logfire-instrumentation/docs/tests.

This is inspired by the approach in haiku.rag, though we adapted it to be a bit closer to the Agent APIs are used (and how you can override model, settings, etc.).

Closes #58

Example:

import asyncio

from pydantic_ai.embeddings import Embedder

embedder = Embedder("openai:text-embedding-3-large")


async def main():
    result = await embedder.embed_documents(["hello", "world"])
    print(result)
    # (IsList, snapshot, and IsDatetime are testing helpers, but you get the point)
    # EmbeddingResult(
    #     embeddings=IsList(
    #         IsList(
    #             snapshot(0.01681816205382347),
    #             snapshot(-0.05579638481140137),
    #             snapshot(0.005661087576299906),
    #             length=1536,
    #         ),
    #         IsList(
    #             snapshot(-0.010592407546937466),
    #             snapshot(-0.03599696233868599),
    #             snapshot(0.030227113515138626),
    #             length=1536,
    #         ),
    #         length=2,
    #     ),
    #     inputs=['hello', 'world'],
    #     input_type='document',
    #     usage=RequestUsage(input_tokens=2),
    #     model_name='text-embedding-3-small',
    #     timestamp=IsDatetime(),
    #     provider_name='openai',
    # )



if __name__ == "__main__":
    asyncio.run(main())

To do:

from pydantic_ai.models.instrumented import InstrumentationSettings
from pydantic_ai.providers import infer_provider

KnownEmbeddingModelName = TypeAliasType(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add a test like this one to verify this is up to date:

def test_known_model_names(): # pragma: lax no cover

@github-actions
Copy link

github-actions bot commented Oct 24, 2025

Docs Preview

commit: 35abadc
Preview URL: https://8ee72db3-pydantic-ai-previews.pydantic.workers.dev

@ggozad
Copy link

ggozad commented Oct 29, 2025

Thanks for starting this and please do let me know if you need help :)
I went quickly through, looks like a great start!

One thing you might want to support from the start is having as part of the EmbeddingSettings is max_context_length and encoding.

Embedding models have a limit of how many tokens of input they can handle. Most providers will raise (openai.BadRequestError iirc for OpenAI, vLLM will return an ugly 500 omg) and then some will say nothing (looking at you Ollama) and just truncate the input so that it fits.

All this is well explained here

I would not necessarily truncate like in the cookbook and still just raise, but I would be grateful to have available from the model side the max_context_length and the encoding so that as a library I can quickly check if a chunk of text fits or not.
Even better if I could get the number of tokens used for some text by a given embedding model.

The only difficulty I see with this is that not all providers expose the tokenizers, for example Ollama does not. But still, would be nice to have it for the providers that do support it, as it's a crucial step when you are trying to chunk a document for embedding.

In haiku.rag, my focus is local models, and like I mentioned Ollama, the popular choice, does not expose a way to tokenize text. So I just do the dumb thing and guesstimate the tokens hoping they are not going to be all that different from some OpenAI model's encoder: I use tiktoken (which you would probably also want to use to support this) and gpt-4o as a "close" model and get an estimate. But I am sure we can do better that this here.

Edit: I am not suggesting that calling embed should calculate the tokens needed on every call. But I imagine that whoever used pydantic AI to embed, would need to also go through the process of chunking some large text, unless they only dealt with embedding queries or simple sentences. So it would be a missed opportunity to not have support for that.

Copy link

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

I would like to be able to comment on the API, but there are no tests showing how to call it.

@DouweM
Copy link
Collaborator

DouweM commented Nov 14, 2025

@gvanrossum I'll make some progress on the PR today, but this is the API as it stands today:

import asyncio

from pydantic_ai.embeddings import Embedder

embedder = Embedder("openai:text-embedding-3-large")


async def main():
    result = await embedder.embed("Hello, world!")
    print(result)


if __name__ == "__main__":
    asyncio.run(main())

With Azure OpenAI you currently have to create the model and provider manually, but we'll make Embedder('azure:text-embedding-3-large') work as well:

import asyncio

from pydantic_ai.embeddings import Embedder
from pydantic_ai.embeddings.openai import OpenAIEmbeddingModel
from pydantic_ai.providers.azure import AzureProvider

model = OpenAIEmbeddingModel("text-embedding-3-large", provider=AzureProvider())

embedder = Embedder(model)


async def main():
    result = await embedder.embed("Hello, world!")
    print(result)


if __name__ == "__main__":
    asyncio.run(main())

@gvanrossum
Copy link

Nice. Do you have a bulk API too? That's essential for typeagent.

@DouweM
Copy link
Collaborator

DouweM commented Nov 14, 2025

@gvanrossum Yep, the embed method is overloaded to take either a str and return list[float], or take Sequence[str] and return list[list[float]], so it's the same method for single and bulk usage. (I'm aware str is itself a Sequence[str], but type checkers appear to handle the overloads correctly.)

@DouweM
Copy link
Collaborator

DouweM commented Nov 15, 2025

@gvanrossum In case you'd like to give it a try pre-release, I've made some progress today, including support for Embedder('azure:...').

@DouweM DouweM changed the title Draft implementation of support for embeddings APIs Support embeddings models Nov 18, 2025
@DouweM
Copy link
Collaborator

DouweM commented Nov 21, 2025

Unfortunately I haven't managed to get to this this week. Next week should be better.

@ggozad
Copy link

ggozad commented Dec 22, 2025

@ggozad @gvanrossum Just curious, what vector DB are you using? I'll want to have a RAG example in our docs.

I use lancedb

@gvanrossum
Copy link

@gvanrossum Just curious, what vector DB are you using?

I am using something I wrote myself. Persistence is optional (currently embeddings are stored in sqlite). Here's the code:
https://github.com/microsoft/typeagent-py/blob/main/typeagent/aitools/vectorbase.py
It stores a list of N normalized embeddings as a 2D numpy array of float32 with dimension (N, embedding_size). This makes for a fast dot product as long as all the vectors fit in memory. :-)

@DouweM DouweM marked this pull request as ready for review December 24, 2025 01:58
@DouweM DouweM merged commit 3717d20 into main Dec 24, 2025
19 of 21 checks passed
@DouweM DouweM deleted the embeddings-api branch December 24, 2025 02:36
@ggozad
Copy link

ggozad commented Dec 24, 2025

Thank you @DouweM & @dmontagu ❤️

@tomaarsen
Copy link
Contributor

Nice work on this!

@paulocoutinhox
Copy link

Hi,

It has support for local embeddings?

@staticmethod
def generate_embeddings(texts):
    import os

    os.environ["TOKENIZERS_PARALLELISM"] = "false"

    from sentence_transformers import SentenceTransformer

    model = SentenceTransformer("all-MiniLM-L6-v2")
    return model.encode(texts, convert_to_numpy=True).tolist()
    ```

@daikeren
Copy link

Hi,

It has support for local embeddings?

@staticmethod
def generate_embeddings(texts):
    import os

    os.environ["TOKENIZERS_PARALLELISM"] = "false"

    from sentence_transformers import SentenceTransformer

    model = SentenceTransformer("all-MiniLM-L6-v2")
    return model.encode(texts, convert_to_numpy=True).tolist()
    ```

Based on document, I think it has.
https://ai.pydantic.dev/embeddings/#sentence-transformers-local

@paulocoutinhox
Copy link

Thanks @daikeren, implemented here and it works.

@Mazyod
Copy link

Mazyod commented Dec 28, 2025

Tiktoken has a subtle footgun that bites us occasionally, which is its lazy loading for tokenizers. We run in an air-gapped environment and best case scenario, it just fails immediately, and the worst case scenario, it hangs the process until it times out.

I haven't tested this update yet, but wanted to share this bit as I do not see any particular support for offline/air-gapped environments.

@paulocoutinhox
Copy link

Thanks @Mazyod.

In my case, I use it with internet access, so it downloads normally.

Also, since you brought up the subject, how do I save it to a Dockerfile so I don't have to download it again when I need to use it in the application?

@Mazyod
Copy link

Mazyod commented Dec 28, 2025

@paulocoutinhox I did this approach for a while, till it failed because tiktoken cache was outdated and it attempted to refetch.

# Cache tiktoken definition from the internet
RUN http_proxy=http://my-proxy \
    https_proxy=http://my-proxy \
    python -c "import tiktoken; tiktoken.encoding_for_model('gpt-4o')"

@DouweM
Copy link
Collaborator

DouweM commented Jan 5, 2026

@Mazyod Good catch about the offline environments; can you please file an issue for that? We can at the very least document a workaround like that one for Docker?

@paulocoutinhox
Copy link

Yeah. Will be nice a solution for this. 💯

@stuaxo
Copy link

stuaxo commented Jan 6, 2026

Does this work with bedrock yet ?

I haven't quite work it out - or maybe something isn't implemented yet ?

By way of example on bedrock (not using pydantic-ai yet) I'm using the embedding models:

"cohere.embed-english-v3" and "amazon.titan-embed-text-v2"

My assumption was that I would prefix these with "bedrock:".

Have the embeddings models available on bedrock been added anywhere in the codebase ?

So far, I get unknown model using various combinations.

@DouweM
Copy link
Collaborator

DouweM commented Jan 6, 2026

@stuartaxonHO The supported providers are documented under https://ai.pydantic.dev/embeddings/#providers; Bedrock is not yet one of them but contribution welcome!

@stuaxo
Copy link

stuaxo commented Jan 6, 2026

@DouweM the word provider is a little overloaded - what should happen, to handle embedding models from different providers on an API provider ?

e.g. Cohere models on bedrock, vs the Amazon models (the model parameters are different and one uses embeddings[0] and the other embedding)

Also - in the code, I noticed there's a list of LLM models, but I can't find one of the embedding models, would that need to be added ?

@DouweM
Copy link
Collaborator

DouweM commented Jan 7, 2026

@stuaxo I agree the provider/model terms are a bit overloaded, we use them as described in https://ai.pydantic.dev/models/overview/#models-and-providers. That means that in this context, "model" maps to "API format", and "provider" maps to "API client/base URL".

So in this case we'd need a BedrockEmbeddingModel that uses their embedding API/SDK, which can be used with the existing BedrockProvider that provide the base URL/API client, and should then support any model name their API does, no matter what company actually build that model.

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.

Vector search and embeddings API