VAROITUS: NÄYTTELEVÄT NÄYTTEET, JOITA EI OLE KÄYTETTY.
Se on todennäköisesti suuri osa siitä, mikä alla on, ei toimi. Minä luon nämä minulle ja sitten teen kaikki askeleet ja saat näytesovelluksen toimimaan...Olet ollut ovela ja nähnyt ne! ne ovat todennäköisesti valmiita joulukuun puolivälissä.
## Johdanto
Tervetuloa 7. osaan4 osa), EU (5 osa) ja paikallisen LLM-integraation (6 osa
Nyt on aika koota kaikki yhteen ja keskittyä siihen, mikä on tärkeintä: tuottaa oikeasti hyödyllisiä kirjoitusehdotuksia.HUOMAUTUS: Tämä on osa kokeilujani tekoälyllä (avustettu suunnittelu) + omalla muokkauksellani.Sama ääni, sama pragmatismi, vain nopeammat sormet.
Me tutkimme edistyneitä
sequenceDiagram
participant User
participant Editor
participant Embedder
participant VectorDB
participant ContextBuilder
participant PromptEngine
participant LLM
participant UI
User->>Editor: Types text
Editor->>Embedder: Generate embedding for context
Embedder->>VectorDB: Search similar content
VectorDB-->>ContextBuilder: Return top K chunks
ContextBuilder->>ContextBuilder: Rank & filter
ContextBuilder->>PromptEngine: Provide context
PromptEngine->>PromptEngine: Build structured prompt
PromptEngine->>LLM: Generate suggestion
LLM-->>UI: Stream response
UI->>User: Display suggestion
tekniikat, kontekstinhallintastrategiat ja se, miten LLM:n saa aikaan ehdotuksia, jotka todella vastaavat kirjoitustyyliäsi.
namespace Mostlylucid.BlogLLM.Core.Services
{
public interface IContextBuilder
{
Task<ContextResult> BuildContextAsync(string currentText, int maxTokens = 2000);
}
public class ContextResult
{
public List<ContextChunk> Chunks { get; set; } = new();
public int TotalTokens { get; set; }
public string CombinedContext { get; set; } = string.Empty;
}
public class ContextChunk
{
public string Text { get; set; } = string.Empty;
public string Source { get; set; } = string.Empty;
public float RelevanceScore { get; set; }
public int TokenCount { get; set; }
}
public class ContextBuilder : IContextBuilder
{
private readonly BatchEmbeddingService _embedder;
private readonly QdrantVectorStore _vectorStore;
private readonly ILogger<ContextBuilder> _logger;
public ContextBuilder(
BatchEmbeddingService embedder,
QdrantVectorStore vectorStore,
ILogger<ContextBuilder> logger)
{
_embedder = embedder;
_vectorStore = vectorStore;
_logger = logger;
}
public async Task<ContextResult> BuildContextAsync(string currentText, int maxTokens = 2000)
{
// Extract meaningful context from current text
var searchText = ExtractSearchContext(currentText);
// Generate embedding
var embedding = _embedder.GenerateEmbedding(searchText);
// Retrieve similar chunks
var searchResults = await _vectorStore.SearchAsync(
queryEmbedding: embedding,
limit: 20, // Get more than we need
languageFilter: "en"
);
// Re-rank results
var rankedChunks = ReRankResults(searchResults, currentText);
// Select chunks that fit in token budget
var selectedChunks = SelectChunks(rankedChunks, maxTokens);
// Build combined context
var combinedContext = BuildCombinedContext(selectedChunks);
return new ContextResult
{
Chunks = selectedChunks,
TotalTokens = selectedChunks.Sum(c => c.TokenCount),
CombinedContext = combinedContext
};
}
private string ExtractSearchContext(string currentText)
{
// Get last 3 paragraphs or 500 characters
var paragraphs = currentText.Split("\n\n");
var lastParagraphs = paragraphs.TakeLast(3);
var searchText = string.Join("\n\n", lastParagraphs);
if (searchText.Length > 500)
{
searchText = searchText.Substring(searchText.Length - 500);
}
return searchText;
}
private List<ContextChunk> ReRankResults(List<SearchResult> results, string currentText)
{
var chunks = new List<ContextChunk>();
foreach (var result in results)
{
var chunk = new ContextChunk
{
Text = result.Text,
Source = $"{result.BlogPostTitle} - {result.SectionHeading}",
RelevanceScore = result.Score,
TokenCount = EstimateTokens(result.Text)
};
// Boost score if from same category
if (SharesCategories(result, currentText))
{
chunk.RelevanceScore *= 1.2f;
}
// Reduce score if very recent (might be too similar)
if (IsVeryRecent(result))
{
chunk.RelevanceScore *= 0.9f;
}
chunks.Add(chunk);
}
return chunks.OrderByDescending(c => c.RelevanceScore).ToList();
}
private List<ContextChunk> SelectChunks(List<ContextChunk> rankedChunks, int maxTokens)
{
var selected = new List<ContextChunk>();
var totalTokens = 0;
foreach (var chunk in rankedChunks)
{
if (totalTokens + chunk.TokenCount > maxTokens)
{
break;
}
selected.Add(chunk);
totalTokens += chunk.TokenCount;
_logger.LogDebug("Selected chunk from {Source} ({Tokens} tokens, score: {Score:F3})",
chunk.Source, chunk.TokenCount, chunk.RelevanceScore);
}
_logger.LogInformation("Selected {Count} chunks totaling {Tokens} tokens",
selected.Count, totalTokens);
return selected;
}
private string BuildCombinedContext(List<ContextChunk> chunks)
{
var sb = new StringBuilder();
for (int i = 0; i < chunks.Count; i++)
{
sb.AppendLine($"=== Reference {i + 1}: {chunks[i].Source} ===");
sb.AppendLine(chunks[i].Text);
sb.AppendLine();
}
return sb.ToString();
}
private int EstimateTokens(string text)
{
// Rough estimate: 1 token ≈ 4 characters
return text.Length / 4;
}
private bool SharesCategories(SearchResult result, string currentText)
{
// Simple heuristic - check if category names appear in current text
// In production, extract actual categories from metadata
return false;
}
private bool IsVeryRecent(SearchResult result)
{
// Reduce score for posts from last 7 days to encourage diversity
// In production, parse actual publish dates
return false;
}
}
}
Tässä on koko virta, jonka toteutamme:
namespace Mostlylucid.BlogLLM.Core.Services
{
p
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.