At our company, we use WordPress as a knowledge base (KB) for internal articles.
We indexed those articles in OpenSearch, but the default keyword search felt… old-school.
- Searching “Netflix subscription” missed “How to manage ネットフリックス plans”.
- Searching “AWS cost optimization” returned random hits because keywords didn’t align.
So we upgraded our search to semantic search using the OpenSearch ML plugin + Cohere embeddings served via Amazon Bedrock — a great combination for multilingual understanding and secure enterprise integration.
⚙️ The Problem
Our initial setup used simple keyword mapping:
{
"properties": {
"title": { "type": "text" },
"content": { "type": "text" }
}
}
Even after tweaking analyzers, users searching in Japanese (katakana) or English weren’t getting expected matches.
For example, “netflix” and “ネットフリックス” should be the same — but OpenSearch treated them as completely different tokens.
🚀 The Plan
We wanted to add semantic search on top of our existing index.
That means converting both documents and queries into vectors using the same embedding model, and comparing them using cosine similarity.
Our plan:
- Create an ML connector for the Cohere embedding API
- Create a model for document embeddings (input_type =
search_document
) - Create another model for query embeddings (input_type =
search_query
) - Update our ingestion pipeline to generate vectors during indexing
- Use script_score (or kNN query) to retrieve the best semantic matches
🔌 Step 1: Create a Connector (to Cohere)
First, we create an ML connector in OpenSearch to call Cohere’s API.
POST _plugins/_ml/connectors/_create
{
"name": "bedrock-cohere-doc-connector",
"description": "Amazon Bedrock connector for Cohere embeddings (document)",
"protocol": "aws_sigv4",
"parameters": {
"region": "us-east-1",
"service_name": "bedrock",
"model_id": "cohere.embed-multilingual-v3",
"input_type": "search_document"
}
}
Here:
protocol: aws_sigv4 makes OpenSearch sign Bedrock API calls with IAM credentials.
model_id refers to Cohere’s multilingual embedding model hosted on Bedrock.
We use input_type=search_document for document-level embeddings.
🧩 Step 2: Register the Model
Now, we create a model using the connector:
POST _plugins/_ml/models/_register
{
"name": "bedrock-cohere-doc-embed",
"function_name": "embedding",
"connector_id": "bedrock-cohere-doc-connector"
}
POST _plugins/_ml/models/bedrock-cohere-doc-embed/_deploy
This model will be used in our ingestion pipeline.
Then deploy it:
POST _plugins/_ml/models/cohere-doc-embed-model/_deploy
OpenSearch now knows how to call Cohere to embed documents.
💾 Step 3: Create the Index with a Vector Field
Next, we create our knowledge base index that includes a vector field for embeddings.
PUT kb-articles
{
"settings": {
"index": {
"knn": true
}
},
"mappings": {
"properties": {
"title": { "type": "text" },
"content": { "type": "text" },
"embedding": {
"type": "knn_vector",
"dimension": 1024,
"method": {
"name": "hnsw",
"space_type": "cosinesimil",
"engine": "nmslib",
"parameters": {
"ef_construction": 512,
"m": 16
}
}
}
}
}
}
The vector field embedding will hold our document-level embeddings from Cohere (1024 dimensions).
🧠 Step 4: Add an Ingest Pipeline
We’ll generate document embeddings automatically during ingestion.
PUT _ingest/pipeline/kb-embed-pipeline
{
"processors": [
{
"ml_inference": {
"model_id": "bedrock-cohere-doc-embed",
"input_map": { "title": "text" },
"output_map": { "embedding": "embedding" }
}
}
]
}
Now, when we index a document:
POST kb-articles/_doc?pipeline=kb-embed-pipeline
{
"title": "Netflix subscription help",
"content": "Steps to manage your Netflix account and billing."
}
OpenSearch automatically calls Amazon Bedrock, retrieves Cohere’s embedding, and stores it in the embedding vector field.
💬 Step 5: Handle User Queries with Another Connector
Initially, I used the same connector (with input_type=search_document) for both documents and queries.
That caused a mismatch — “ネットフリックス” (Katakana) and “Netflix” were still not matching.
The fix was to create another connector and model specifically for query embeddings.
POST _plugins/_ml/connectors/_create
{
"name": "bedrock-cohere-query-connector",
"description": "Amazon Bedrock connector for Cohere embeddings (query)",
"protocol": "aws_sigv4",
"parameters": {
"region": "us-east-1",
"service_name": "bedrock",
"model_id": "cohere.embed-multilingual-v3",
"input_type": "search_query"
}
}
and then register and deploy another model:
POST _plugins/_ml/models/_register
{
"name": "bedrock-cohere-query-embed",
"function_name": "embedding",
"connector_id": "bedrock-cohere-query-connector"
}
POST _plugins/_ml/models/bedrock-cohere-query-embed/_deploy
This ensures both documents and queries are embedded in compatible vector spaces.
Now, when we run a search, we first generate a query embedding via the ML model’s /predict
endpoint:
POST _plugins/_ml/models/bedrock-cohere-query-embed/_predict
{
"text": "ネットフリックス subscription"
}
🔍 Step 6: Semantic Search Query
Finally, we plug the embedding into a script_score
query to rank results by cosine similarity:
POST kb-articles/_search
{
"size": 5,
"query": {
"script_score": {
"query": { "match_all": {} },
"script": {
"source": "cosineSimilarity(params.query_vector, 'embedding') + 1.0",
"params": {
"query_vector": [/* embedding array from _predict */]
}
}
}
},
"sort": [
{ "_score": { "order": "desc" } }
]
}
Now “Netflix” and “ネットフリックス” both match beautifully.
🎯 Cohere’s multilingual embeddings + OpenSearch vector search did the trick.
🧩 Why We Chose Cohere
- Strong multilingual understanding — perfect for English + Japanese content
- Easy integration — just plug in an API key via OpenSearch ML connector
-
Consistent embedding dimensions — works well with
knn_vector
- Fast inference — good for production-scale pipelines
🪛 Troubleshooting Tips
Problem | Cause | Fix |
---|---|---|
Katakana & English not matching | Used search_document for query embeddings |
Create a separate connector with input_type=search_query
|
“Dimension mismatch” errors | Wrong embedding model or field dimension | Make sure both model & field use same dimension
|
Inference timeout | Cohere API rate limits | Batch or cache embeddings during ingestion |
Weird scores | Missing space_type: cosinesimil
|
Use cosine similarity in mapping |
✅ Summary
We started with basic keyword search and ended up with multilingual semantic search using:
- OpenSearch ML plugin
- Cohere embedding models
- Cosine similarity on
knn_vector
- Separate connectors for documents vs. queries
Now users can search KB articles naturally — whether they type “Netflix” or “ネットフリックス”.
Same meaning, same results.
💡 If you’re building multilingual or brand-sensitive search, don’t skip the input_type
difference — it can make or break your semantic matching.
Top comments (0)