Intelligent Matching and Semantic Search for Marketplace Applications Using OpenAI and .NET
Learn how to build AI-powered semantic search and intelligent matching systems for marketplace platforms using OpenAI embeddings and .NET.
Join the DZone community and get the full member experience.
Join For FreeMarketplace platforms are fundamentally matching systems. Whether the platform connects:
- Students and tutors
- Freelancers and clients
- Buyers and sellers
- Consultants and companies
The overall user experience usually depends on how accurately the platform can connect relevant people together.
At early stages, traditional search systems are often enough. Basic SQL filtering, category-based navigation, and keyword matching can solve many initial requirements without major issues.
The situation changes once the platform grows and users begin writing longer, intent-based queries instead of simple keywords.
For example, a user may search for:
“online calculus tutor for engineering preparation”
while a marketplace listing may contain:
“advanced mathematics mentor for university students”
Even though both sides are highly relevant, a traditional keyword-based search engine may completely fail to connect them because the wording is different.
This is one of the areas where semantic search architectures become extremely valuable.
Why Traditional Marketplace Search Starts Breaking Down
Most marketplace platforms initially rely on:
- SQL
LIKEqueries - full-text search
- BM25 ranking
- tag filtering
These approaches work reasonably well when search queries are short and predictable. However, real users rarely search using the exact same terminology as listing owners. A few common examples:
| User Query | Marketplace Listing |
|---|---|
| math tutor | calculus mentor |
| IELTS coach | English speaking trainer |
| React mentor | frontend architect |
| startup advisor | business consultant |
The issue here is not syntax. It is the semantic meaning. Traditional search engines are effective at matching identical words, but much weaker at understanding contextual similarity between different phrases.
Intent Matters More Than Exact Keywords
One thing that becomes visible fairly quickly in marketplace applications is that users tend to search with intent instead of isolated keywords.
For example:
“senior React mentor for interview preparation”
The user is probably not simply searching for “React.” The actual intent may include:
- Mentorship
- Senior-level expertise
- Interview coaching
- Frontend architecture experience
Traditional keyword search systems struggle to interpret these relationships properly.
Even when partially relevant results appear, ranking quality often becomes inconsistent as queries become longer and more contextual.
Semantic Search Approaches the Problem Differently
Semantic search systems do not treat text as isolated keywords. Instead, text is converted into vector representations called embeddings.
These embeddings represent contextual meaning rather than exact wording.
As a result, the following phrases can become mathematically close to each other even when they do not contain identical words:
- “Math tutor”
- “Calculus mentor”
- “Engineering mathematics coach”
This allows marketplace applications to perform much more flexible matching.
A Typical Semantic Search Architecture
User Query
↓
Embedding Generation
↓
Vector Search
↓
Similarity Scoring
↓
Hybrid Ranking
↓
Marketplace Results
The important detail here is that the following are converted into embeddings before similarity calculations happen:
- Marketplace listings
- User queries
.NET Technologies Used in the Architecture
A typical .NET-based semantic search stack may include the following components:
| Area | Technology |
|---|---|
| API Layer | ASP.NET Core |
| Background Jobs | Hosted Services / Quartz.NET |
| Queue System | RabbitMQ / Azure Service Bus |
| Database | SQL Server / PostgreSQL |
| Vector Storage | pgvector / Pinecone |
| Cache | Redis |
| Logging | Serilog |
| Monitoring | OpenTelemetry |
| AI Integration | OpenAI API |
One thing that becomes obvious during implementation is that the OpenAI API itself is usually only a small part of the overall system.
The larger engineering effort often involves:
- Indexing
- Ranking
- Caching
- Asynchronous processing
- Operational monitoring
- Retry handling
Marketplace Listing Model
A simplified marketplace listing model may look like this:
public class MarketplaceListing
{
public long Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string CategoryName { get; set; }
public string Location { get; set; }
public bool IsActive { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? LastIndexedOn { get; set; }
public string SearchText =>
$"{Title} {Description} {CategoryName} {Location}";
}
The SearchText property combines multiple searchable fields into a single semantic context before embedding generation.
Generating Embeddings With OpenAI
A simplified embedding service implementation in .NET may look like this:
public class OpenAIEmbeddingService : IEmbeddingService
{
private readonly HttpClient _httpClient;
private readonly IConfiguration _configuration;
public OpenAIEmbeddingService(
HttpClient httpClient,
IConfiguration configuration)
{
_httpClient = httpClient;
_configuration = configuration;
}
public async Task<float[]> GenerateEmbeddingAsync(
string input,
CancellationToken cancellationToken = default)
{
var apiKey = _configuration["OpenAI:ApiKey"];
using var request = new HttpRequestMessage(
HttpMethod.Post,
"https://api.openai.com/v1/embeddings");
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", apiKey);
var body = new
{
model = "text-embedding-3-small",
input = input
};
request.Content = new StringContent(
JsonSerializer.Serialize(body),
Encoding.UTF8,
"application/json");
using var response =
await _httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
var json =
await response.Content.ReadAsStringAsync(cancellationToken);
using var document = JsonDocument.Parse(json);
return document
.RootElement
.GetProperty("data")[0]
.GetProperty("embedding")
.EnumerateArray()
.Select(x => x.GetSingle())
.ToArray();
}
}
Dependency Injection
builder.Services.AddHttpClient<
IEmbeddingService,
OpenAIEmbeddingService>();
builder.Services.AddScoped<
ISemanticSearchService,
SemanticSearchService>();
Background Indexing
One issue that appears very quickly in production systems is latency. Generating embeddings synchronously during listing creation or updates may slow down the request lifecycle significantly.
Because of this, many systems move embedding generation into:
- Background workers
- Queues
- Asynchronous indexing pipelines
A simple hosted worker example:
public class ListingEmbeddingWorker : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<ListingEmbeddingWorker> _logger;
public ListingEmbeddingWorker(
IServiceProvider serviceProvider,
ILogger<ListingEmbeddingWorker> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(
CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
using var scope =
_serviceProvider.CreateScope();
var service =
scope.ServiceProvider
.GetRequiredService<IListingEmbeddingService>();
await service.IndexPendingListingsAsync(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Listing embedding worker failed.");
}
await Task.Delay(
TimeSpan.FromMinutes(5),
stoppingToken);
}
}
}
Vector Similarity
Once embeddings are generated, similarity calculations can be performed. The most common approach is cosine similarity:
cos(θ)=A⋅B∥A∥∥B∥\cos(\theta)=\frac{A\cdot B}{\|A\|\|B\|}cos(θ)=∥A∥∥B∥A⋅B
A simplified helper implementation may look like this:
public static class VectorSimilarityHelper
{
public static double CosineSimilarity(
float[] vectorA,
float[] vectorB)
{
double dotProduct = 0;
double magnitudeA = 0;
double magnitudeB = 0;
for (int i = 0; i < vectorA.Length; i++)
{
dotProduct += vectorA[i] * vectorB[i];
magnitudeA += vectorA[i] * vectorA[i];
magnitudeB += vectorB[i] * vectorB[i];
}
return dotProduct /
(Math.Sqrt(magnitudeA) * Math.Sqrt(magnitudeB));
}
}
Semantic Similarity Alone Is Usually Not Enough
One thing that became obvious during testing was that semantic similarity alone sometimes produced weak ranking behavior.
For example, the following could still receive high semantic scores:
- Inactive listings
- Outdated profiles
- Low-quality marketplace entries
Because of this, most production marketplace systems eventually move toward hybrid ranking models.A simplified ranking formula may look like this:
FinalScore=0.45SemanticScore+0.25KeywordScore+0.15PopularityScore+0.10FreshnessScore+0.05ConversionScoreFinalScore=0.45SemanticScore+0.25KeywordScore+0.15PopularityScore+0.10FreshnessScore+0.05ConversionScoreFinalScore=0.45SemanticScore+0.25KeywordScore+0.15PopularityScore+0.10FreshnessScore+0.05ConversionScore
This combines:
- semantic relevance
- keyword relevance
- popularity
- freshness
- conversion metrics
There is usually no perfect ranking formula. In practice, ranking becomes an ongoing optimization problem.
Example Semantic Search Service
public class SemanticSearchService : ISemanticSearchService
{
private readonly AppDbContext _dbContext;
private readonly IEmbeddingService _embeddingService;
public SemanticSearchService(
AppDbContext dbContext,
IEmbeddingService embeddingService)
{
_dbContext = dbContext;
_embeddingService = embeddingService;
}
public async Task<List<SearchResultDto>> SearchAsync(
string query,
CancellationToken cancellationToken)
{
var queryVector =
await _embeddingService.GenerateEmbeddingAsync(
query,
cancellationToken);
var listings = await _dbContext.ListingEmbeddings
.Include(x => x.Listing)
.ToListAsync(cancellationToken);
var results = listings
.Select(x =>
{
var vector =
JsonSerializer.Deserialize<float[]>(x.VectorJson);
var similarity =
VectorSimilarityHelper.CosineSimilarity(
queryVector,
vector);
return new SearchResultDto
{
ListingId = x.ListingId,
Title = x.Listing.Title,
SimilarityScore = similarity
};
})
.OrderByDescending(x => x.SimilarityScore)
.Take(50)
.ToList();
return results;
}
}
Production Challenges
One thing that often gets underestimated in semantic search discussions is operational complexity. The AI layer itself is usually easier than the surrounding production engineering. A few examples include:
- Embedding costs
- Queue management
- Indexing latency
- Retry handling
- Stale embeddings
- Cache invalidation
- Multilingual relevance
- Ranking quality optimization
For example, the following trigger embedding generation, API costs can grow much faster than expected:
- Listing update
- Profile edit
- Search query
Caching and embedding reuse become important fairly early in the process.
Final Thoughts
Semantic search is not really about replacing traditional search entirely. In most production systems, the better approach is usually to combine these into a layered ranking architecture:
- Semantic relevance
- Keyword matching
- Behavioral scoring
- Freshness
- Business metrics
OpenAI embeddings and .NET provide a practical foundation for building these types of marketplace systems, especially for platforms where relevance quality directly affects user experience and conversion rates.
One interesting observation after introducing semantic matching is that users generally spend less time trying to “guess the correct keywords.” The platform becomes significantly better at understanding what users actually mean instead of simply matching individual words.
Opinions expressed by DZone contributors are their own.
Comments