Semantic Vector Search for Mechanical Drawing Titles with .NET Core and Open-Source Models

Mechanical drawing titles often contain specialized terminology, abbreviations, and non-standard formats (e.g., "Potato Sorter DWG-001 Stainless Steel"). Traditional keyword-based search struggles with such data due to semantic ambiguity and varied user queries (e.g., "Potato Screening Machine"). Semantic search enhancement addresses these limitations by leveraging deep learning models to understand contextual meaning.

System Architecture

The solution integrates multiple components:

  • Frontend: A WeChat mini-program for query input.
  • Backend: A .NET Core application handling business logic and orchestrating services.
  • Embedding Service: A Python API that generates vector embeddings from text using a pre-trained model (e.g., bge-large-zh-noinstruct).
  • Vector Database: Redis, configured with the RediSearch module for efficient storage and K-Nearest Neighbors (KNN) search.

Data flows as follows:

  1. Drawing titles are processed by the .NET Core app, sent to the Python API for embedding generation, and stored in Redis.
  2. User queries undergo the same embedding process and are used to search the Redis vector index.

Imlpementation

Generating and Storing Title Embeddings

A Python service generates dense vector representations of drawing titles.

from FlagEmbedding import FlagModel
import numpy as np

class EmbeddingGenerator:
    def __init__(self, model_path):
        self.model = FlagModel(
            model_path,
            query_instruction_for_retrieval="Represent this sentence for searching relevant passages:",
            use_fp16=True
        )
    
    def generate(self, text):
        return self.model.encode(text)

# Usage
generator = EmbeddingGenerator("./model")
title_vector = generator.generate("Potato Sorter DWG-001")

The .NET Core application calls this service via HTTP.

public class EmbeddingServiceClient
{
    private readonly HttpClient _httpClient;
    private readonly string _serviceEndpoint;

    public EmbeddingServiceClient(HttpClient httpClient, string endpoint)
    {
        _httpClient = httpClient;
        _serviceEndpoint = endpoint;
    }

    public async Task<float[]> GetEmbeddingAsync(string text)
    {
        var payload = new { text };
        var requestContent = new StringContent(
            JsonSerializer.Serialize(payload),
            Encoding.UTF8,
            "application/json"
        );

        var response = await _httpClient.PostAsync(_serviceEndpoint, requestContent);
        response.EnsureSuccessStatusCode();

        var responseBody = await response.Content.ReadAsStringAsync();
        var result = JsonSerializer.Deserialize<EmbeddingResult>(responseBody);
        return result?.Vector ?? Array.Empty<float>();
    }
}

public class EmbeddingResult
{
    public float[] Vector { get; set; }
}

Generated vectors are stored in Redis.

public class RedisVectorStorage
{
    private readonly IDatabase _database;
    private readonly string _keyPrefix;
    private readonly string _indexName;

    public RedisVectorStorage(IConnectionMultiplexer redis, string prefix, int dbIndex = 0)
    {
        _database = redis.GetDatabase(dbIndex);
        _keyPrefix = prefix;
        _indexName = $"{prefix}_idx";
    }

    public void SetupIndex(int dimensions)
    {
        var indexExists = _database.Execute("FT.INFO", _indexName).ToString() != "Unknown Index name";
        if (indexExists) return;

        var schema = new Schema()
            .AddTextField("id")
            .AddVectorField("vec", VectorField.VectorAlgo.FLAT, new Dictionary<string, object>
            {
                ["TYPE"] = "FLOAT32",
                ["DIM"] = dimensions,
                ["DISTANCE_METRIC"] = "COSINE"
            });

        _database.Execute("FT.CREATE", _indexName, "ON", "HASH", "PREFIX", "1", $"{_keyPrefix}:", "SCHEMA", schema);
    }

    public void SaveVector(string id, float[] vector)
    {
        var key = $"{_keyPrefix}:{id}";
        var vectorBytes = new byte[vector.Length * sizeof(float)];
        Buffer.BlockCopy(vector, 0, vectorBytes, 0, vectorBytes.Length);

        var entries = new HashEntry[]
        {
            new HashEntry("id", id),
            new HashEntry("vec", vectorBytes)
        };
        _database.HashSet(key, entries);
    }
}

Execuitng a Similarity Search

To find relevant drawings, the user's query is embedded and matched against stored vectors.

public class SearchExecutor
{
    private readonly EmbeddingServiceClient _embeddingClient;
    private readonly RedisVectorStorage _vectorStore;

    public SearchExecutor(EmbeddingServiceClient ec, RedisVectorStorage vs)
    {
        _embeddingClient = ec;
        _vectorStore = vs;
    }

    public async Task<List<string>> FindRelevantDrawingsAsync(string query, int maxResults)
    {
        var queryVector = await _embeddingClient.GetEmbeddingAsync(query);
        if (queryVector.Length == 0) return new List<string>();

        var vectorBytes = new byte[queryVector.Length * sizeof(float)];
        Buffer.BlockCopy(queryVector, 0, vectorBytes, 0, vectorBytes.Length);

        var knnQuery = $"*=>[KNN {maxResults} @vec $query_vec AS score]";
        var parameters = new Dictionary<string, object> { ["query_vec"] = vectorBytes };

        // Execute search and return IDs
        return new List<string>();
    }
}

Tags: Semantic Search .NET Core vector database Redis Embeddings

Posted on Fri, 12 Jun 2026 16:08:47 +0000 by sudhakararaog