<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:media="http://search.yahoo.com/mrss/" >

<channel>
	<title>Real-Life Projects &#8211; SaM Solutions</title>
	<atom:link href="https://sam-solutions.com/blog/category/real-life-projects/feed/" rel="self" type="application/rss+xml" />
	<link>https://sam-solutions.com</link>
	<description></description>
	<lastBuildDate>Mon, 15 Jun 2026 11:23:37 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>
	<item>
		<title>Testing an LLM Chatbot in an MCP System</title>
		<link>https://sam-solutions.com/blog/llm-chatbot-testing/</link>
					<comments>https://sam-solutions.com/blog/llm-chatbot-testing/#respond</comments>
		
		<dc:creator><![CDATA[Mikhail Sinkin]]></dc:creator>
		<pubDate>Thu, 07 May 2026 14:41:33 +0000</pubDate>
				<guid isPermaLink="false">https://sam-solutions.com/?post_type=article&#038;p=39594</guid>

					<description><![CDATA[(If you prefer video content, please watch the concise video summary of this article below) Introduction: The Paradigm Shift in Quality Assurance for LLM-Based Systems Testing an LLM chatbot inside an MCP-based system differs from testing classical software. Traditional systems are deterministic: the same input produces the same output. In a typical REST API, a [&#8230;]]]></description>
										<content:encoded><![CDATA[<span id="more-39594"></span>
<!--noteaser-->



<iframe style="margin: 0;" width="100%" height="115" scrolling="no" frameborder="no" allow="autoplay" title="Testing an LLM Chatbot in an MCP System" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/soundcloud%253Atracks%253A2318411675&#038;color=%23ff5500&#038;auto_play=false&#038;hide_related=false&#038;show_comments=false&#038;show_user=false&#038;show_reposts=false&#038;show_teaser=false"></iframe><p style="font-size:14px;"><em>(If you prefer video content, please <a href="#video-content">watch the concise video summary</a> of this article below)</em></p>




 
    
    <div class="editor-content editor-content_style_1 editor-content_index_1">
        
    
    <div class="editor-content__descr">
        <div class="wysiwyg-editor"><h2>Key Takeaways</h2>
<ul>
<li><b>Determinism no longer applies: </b>LLM chatbot testing shifts from exact-match assertions to probabilistic, semantic validation, where multiple correct answers can exist for the same input.</li>
<li><b>Architecture defines test complexity:</b> MCP orchestration, RAG pipelines, tool calls, and streaming responses create multiple failure points, making root-cause analysis inherently multi-layered.</li>
<li><b>Validation must be multi-dimensional: </b>Combining must-have, must-not, and semantic similarity checks is essential to balance flexibility with control and reduce hallucination risks.</li>
<li><b>Test results are context- and configuration-dependent: </b>Model version, prompt design, inference settings, and conversation history all influence outcomes, requiring continuous tuning and iterative test refinement.</li>
</ul>
</div>
    </div>
    </div>
    



<h2 class="wp-block-heading"><strong>Introduction: The Paradigm Shift in Quality Assurance for LLM-Based Systems</strong></h2>



<p class="wp-block-paragraph">Testing an LLM chatbot inside an MCP-based system differs from testing classical software. Traditional systems are deterministic: the same input produces the same output. In a typical REST API, a request either returns the expected JSON payload or it does not. Assertions are straightforward.</p>



<p class="wp-block-paragraph">A chatbot built around a large language model behaves differently. Testing a <a href="/blog/llm-architecture/">Large Language Model (LLM)</a> output requires a fundamental paradigm shift. The assumptions that have governed software testing for decades — determinism, exact reproducibility, and binary state validation — break down when confronted with generative AI.</p>



<p class="wp-block-paragraph">To understand the complexity of testing these applications, we must first look at the underlying architecture, explore why traditional assertions fail, and examine the unique, context-dependent pitfalls and specifics.</p>




 
    
    <div class="editor-list-cta editor-list-cta_style_1 editor-list-cta_index_2">
        
    <div class="editor-list-cta__items">
                                    
                    			    				<style>
    					.editor-list-cta_index_2 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-button {
            				            				
            				            				        							    background: linear-gradient(to right, #a067e8, #527eff);
    							            				    					}
    				</style>
    			    			
    			    			    				<style>
    					.editor-list-cta_index_2 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left {
    					    padding-left: 10px;
    					}
    					
    					.editor-list-cta_index_2 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					    left: 0;
    					    top: 50%;
    					    width: 3px;
    					    content: '';
    					    position: absolute;
    					    margin-left: -10px;
    					    height: calc(100% + 10px);
    					    transform: translateY(-50%);

            				            					    							    background: linear-gradient(45deg, #527eff, #a067e8);
    							            				    					}
    					
    					@media (max-width: 475px) {
    					    .editor-list-cta_index_2 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					        width: 2px;
                                margin-left: -3.5px;
    					    }
    					}
    				</style>
    			                
                    			    			        
    			
                    			
                                
                <div class="editor-list-cta__item editor-list-cta__item_index_1">
                    <div class="editor-list-cta__item-inner">
            		                		                                <div class="editor-list-cta__item-left">
                                                                                                    <div class="editor-list-cta__item-title"><p><strong>Reap the benefits of high quality software applications</strong> with SaM Solutions’ expert QA and testing services.</p>
</div>
                                                	        </div>
            	        
            	                    	            <div class="editor-list-cta__item-right">
            	                                    	                                	                                	            <div class="editor-list-cta__item-event">
                    	                <div class="editor-list-cta__item-button button button_style_1">
                                            <a class="button__inner" href="/services/qa-services/?utm_source=blog&#038;utm_medium=post_ID_39594&#038;utm_campaign=cta_post_content_3970">                                                <div class="button__name">Learn more</div>
                                            </a>                                        </div>
                                    </div>    
                                                                        
            	            </div>
            	                	        </div>
                </div>
                        </div>
    </div>
    



<h2 class="wp-block-heading"><strong>System Architecture Overview</strong></h2>



<p class="wp-block-paragraph">A modern LLM-based chatbot is a complex, multi-layered distributed system under the hood where each component introduces new variables into the testing equation.</p>



<p class="wp-block-paragraph">When a user submits a prompt, it travels through several critical server-side components before a response is generated. Initially, the input is often processed by an orchestrator or reasoning engine. In enterprise environments like ours, this is typically where the <a href="/blog/model-context-protocol/">Model Context Protocol (MCP)</a> comes into play. MCP allows the LLM to securely interact with external data sources and internal tools without hardcoding integrations.&nbsp;</p>



<p class="wp-block-paragraph">Simultaneously, the system employs a <a href="/blog/rag-llm-architecture/">Retrieval-Augmented Generation (RAG)</a> pattern. Before the LLM generates a response, the user&#8217;s query is embedded and sent to a vector database to retrieve semantically relevant context. This retrieved context, along with system instructions and chat history, is dynamically injected into a hidden meta-prompt. Only then is the payload sent to the inference engine (the model serving layer). Finally, the LLM generates tokens sequentially, which are streamed back to the client via a persistent connection, such as WebSockets using SignalR.</p>



<h2 class="wp-block-heading"><strong>Challenge</strong></h2>



<p class="wp-block-paragraph">These architectural decisions directly impact testability. Testing the “chatbot” means simultaneously testing the retrieval mechanisms, the orchestration layer, and the generative model itself. Therefore, failures in such an environment rarely come from a single place. If the chatbot answers incorrectly, the cause may be:</p>



<ul class="wp-block-list">
<li>retrieval returned irrelevant documents</li>



<li>the prompt not optimized properly for use cases</li>



<li>the correct document was retrieved but the model ignored it</li>



<li>the model invented information not present in the context</li>



<li>the chatbot did not call a tool to trigger specific action or retrieve the specific data</li>



<li>the tool returned an error that was not propagated to the model</li>



<li>the context window truncated relevant information</li>
</ul>



<p class="wp-block-paragraph">The chatbot also operates inside a conversation. A response may depend on previous turns, retrieved documents, system prompts, and tool outputs. Testing a single prompt in isolation does not always reproduce the behavior seen in real conversations.</p>



<p class="wp-block-paragraph">The business context adds pressure. In this system, the chatbot appears on a company website and answers questions from potential clients about the company’s experience and projects. If the bot invents projects or misunderstands a request, the damage goes beyond incorrect information. It can actively simulate successful lead handling, confirming that a contact request or submission has been sent to a sales team when in reality no downstream process has been triggered. The result is a broken conversion flow: the user believes a handoff to a human agent has occurred, while no lead is recorded, no notification is sent, and no follow-up ever happens!</p>



<p class="wp-block-paragraph">Because of this, testing required a combination of traditional QA techniques and evaluation methods designed for LLM systems.</p>



<h3 class="wp-block-heading">Fundamental differences in testing LLM output vs. deterministic systems</h3>



<p class="wp-block-paragraph">Classical software testing is built on determinism: given state *A* and input *B*, you expect that the system returns output *C*. If it returns *D*, you report a bug.</p>



<p class="wp-block-paragraph">LLMs are inherently probabilistic. They calculate a probability distribution over the next possible token in a sequence. Consequently, identical inputs can produce different outputs. This non-deterministic nature obliterates traditional regression testing workflows. If you write an exact-match assertion expecting the bot to say, “The application is a <a href="/services/web-app-development-services/">web-based</a> SaaS platform,” and the bot instead replies, “The software is an online platform delivered via SaaS,” a deterministic test fails.&nbsp;</p>



<p class="wp-block-paragraph">This introduces the semantic correctness problem. An LLM&#8217;s output can be grammatically distinct, utilize different vocabulary, and be structured entirely differently, yet remain 100% factually accurate and valid.&nbsp;</p>



<p class="wp-block-paragraph">Because of this, traditional bug classification and reproducibility workflows break down. A QA engineer cannot easily attach a “steps to reproduce” ticket for an LLM hallucination, because following those exact steps five minutes later may yield a perfect response.&nbsp;</p>



<h3 class="wp-block-heading">Configuration-dependent nature of system output</h3>



<p class="wp-block-paragraph">Even when employing advanced semantic testing, QA teams must navigate a minefield of configuration-dependent variables that make test suites uniquely fragile.</p>



<p class="wp-block-paragraph">First, test validity is tightly coupled to specific model versions. Different models have their own specifics. A test suite becomes a snapshot of expected behavior for a specific model at a specific time.</p>



<p class="wp-block-paragraph">Second, inference settings like `Temperature` (which controls randomness) and `Top-P` (which controls vocabulary diversity) act as hidden test variables. A suite that is somewhat stable at Temperature 0.2 may become less deterministic at Temperature 0.7.</p>



<p class="wp-block-paragraph">Furthermore, these tests are hyper-sensitive to system configuration. Small adjustments to the system prompt, even seemingly innocuous wording changes, can drastically alter the downstream outputs.&nbsp;</p>



<p class="wp-block-paragraph">This leads to a persistent challenge: distinguishing system regressions from expected variance. When a test fails, the team must determine if the system actually broke (e.g., the RAG database went offline) or if the model merely generated a statistically improbable, but acceptable, variation of the answer that the semantic evaluator wasn&#8217;t tuned to handle.</p>



<p class="wp-block-paragraph">Finally, multi-turn conversations introduce severe state pollution. Because the model relies on conversation history, an imperfect answer in turn one can corrupt the LLM&#8217;s context window for turn three. Testing multi-turn flows requires isolating the state, carefully managing the conversational context, and continuously re-validating the entire suite as the system evolves.</p>



<p class="wp-block-paragraph">Thus, a test captures a constrained observation window: a single slice of behavior produced by a given model version, decoding configuration, system prompt, input prompt, and retrieval and conversation state. It represents one trajectory through a much larger probabilistic space of possible outputs.</p>



<p class="wp-block-paragraph">***</p>



<p class="wp-block-paragraph">The following paragraph details exactly how we built a tool to meet these challenges head-on.</p>



<h2 class="wp-block-heading"><strong>Functional Testing</strong></h2>



<p class="wp-block-paragraph">First of all, the list of use cases has been created. <a href="/services/qa-services/functional-testing-services/">Functional testing</a> started with the main user scenarios expected on the website.&nbsp;</p>



<p class="wp-block-paragraph">Typical questions included:</p>



<ul class="wp-block-list">
<li>experience in specific industries</li>



<li>technologies used for <a href="/services/back-end-development-services/">back-end</a> or <a href="/services/front-end-development-services/">front-end development</a></li>



<li>examples of previous projects</li>



<li>rough project estimates</li>



<li>how to contact the sales team</li>
</ul>



<p class="wp-block-paragraph">Visitors usually ask about the company’s experience, technologies, and previous projects. Some conversations also lead to contact requests.</p>



<p class="wp-block-paragraph">Later this list was expanded to test cases. Each test case is structured as an ordinary one, but has some specific inherent to <a href="https://sam-solutions.com/services/ai-software-development/">AI-powered systems</a>. There are the sections describing what must be, what is appropriate in response, and what must not be in it in any circumstances.</p>



<p class="wp-block-paragraph">User’s question such as “Have you built any <a href="/industries/medicine-healthcare-software-development/">healthcare</a> platforms before?” should produce an answer based on portfolio data stored in the knowledge base. Basically, the answer should mention real projects if they exist and avoid inventing clients.</p>



<p class="wp-block-paragraph">Here is the story of how we built a custom, end-to-end Python-based test harness designed for end-to-end validation of streaming chatbot responses.</p>



<h3 class="wp-block-heading">The challenge: WebSockets and non-deterministic outputs</h3>



<p class="wp-block-paragraph">The chatbot streams tokens sequentially via SignalR over WebSockets. We couldn&#8217;t just fire off an HTTP POST and read the JSON response. Therefore we created a modular <a href="/services/technologies/python-development-services/">Python</a> framework broken down into a SignalR client, an evaluation engine, and a streamlined test runner.</p>



<p class="wp-block-paragraph">It has been designed considering the separation of concerns principle: test data (JSON-based test cases and validation rules) is decoupled from the transport layer (SignalR/WebSockets), interpretation logic (NLP analysis), and the execution runner.</p>



<h3 class="wp-block-heading">Building the SignalR Client</h3>



<p class="wp-block-paragraph">The first step was establishing communication. Since our chatbot works via SignalR, we opted for the lightweight `websocket-client` library in Python rather than pulling in heavy browser automation tools like Playwright or Selenium, as our goal was to test the <a href="/services/software-engineering/api-development-services/">API</a>/back-end logic directly (Integration/E2E level without the UI overhead).</p>



<p class="wp-block-paragraph">SignalR has its own quirks. It requires a specific JSON handshake (`{&#8220;protocol&#8221;: &#8220;json&#8221;, &#8220;version&#8221;: 1}`) and appends a very specific terminating character (`\x1e`) to the end of every payload.&nbsp;</p>



<p class="wp-block-paragraph">Our client script establishes the WebSocket connection, manages the handshake, and enters a `while True` listening loop. Because the LLM streams its response by small chunks of data, the client parses incoming `ReceiveMessage` events, concatenating the text chunks until it receives an `isComplete: True` flag from the server, at which point it gracefully closes the socket and passes the complete string to our evaluator.</p>



<h3 class="wp-block-heading">The three-layered validation strategy</h3>



<p class="wp-block-paragraph">Once we had the full text string from the chatbot, we needed to decide if it was &#8220;correct&#8221;. We implemented a three-tiered quality gate:</p>




 
    
    <div class="editor-list-step editor-list-step_style_1 editor-list-step_index_3">
        
    <div class="editor-list-step__items">
                                    <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">The “must-have” check (with synonyms)</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>While LLMs vary their phrasing, there are often hard business requirements regarding what must be mentioned. Using a JSON-driven test data approach, we define `must_have` arrays. To prevent flakiness, we built a synonym engine.</p>
<p>For example, if the test requires the bot to mention the application is &#8220;web-based&#8221;, our test data maps &#8220;web-based&#8221; to `[&#8220;saas&#8221;, &#8220;online platform&#8221;, &#8220;web application&#8221;, &#8220;AJAX-based&#8221;]`. If the bot uses any of those terms, the assertion passes.</p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">The “must-not” check (hallucination prevention)</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>Equally important to what the bot says is what it should not say. AI models are prone to hallucination. If a user asks about a legacy accounting web app, the bot shouldn&#8217;t invent features. We feed the framework a `must_not` array containing terms like “mobile app”, “blockchain”, or “AI analytics”. If these are detected, the test immediately fails.</p>
<p>This mechanism forms a baseline validation layer. In most cases it produces stable and predictable results because it operates on explicit lexical constraints.</p>
<p>However, this stability is still superficial. For example, the absence of a term does not imply correctness. We had to run the test suite multiple times to expose flaky outputs, iteratively expanding the <i>must_have</i> set with additional terms until the results reached a level of reliability suitable for interpretation.</p>
<p>The weakest component in this setup is the<i> must_not </i>block itself. It assumes that undesired behavior can be exhaustively enumerated. In practice this is impossible.</p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">Semantic similarity (the AI testing the AI)</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>We still should keep in mind that even if all keywords are present, the sentence structure could be completely wrong.</p>
<p>To solve this, we integrated `sentence-transformers` backed by `torch` and `scikit-learn`. We load the `all-MiniLM-L6-v2` model — a fast, lightweight NLP model perfect for calculating sentence embeddings.</p>
<p>When a test runs, we take the bot&#8217;s generated response and a pre-defined `expected_answer` from our JSON test cases (basically it’s taken directly from the data source). We convert both strings into high-dimensional vector embeddings and calculate the cosine similarity. If the similarity score drops below `0.70` (70%, which is also an empirical value, set after several iterations of test execution), the test fails. This allows our chatbot to use completely different sentence structures and vocabulary, yet still pass the test as long as the fundamental semantic meaning remains intact.</p>
<p>We consider a test passed only when it passes all three layers.</p>
</div>
                        </div>
                                    </div>  
                        
    </div>
    </div>
    



<h3 class="wp-block-heading">Decoupling logic from data: The JSON test case structure</h3>



<p class="wp-block-paragraph">One of the most critical architectural decisions we made early on was to strictly separate the test execution logic from the test data and validation rules. Rather than hardcoding test scenarios into Python scripts, we externalized everything into a structured JSON file.</p>



<p class="wp-block-paragraph">This created a pristine separation of concerns: the Python runner handles the how (transport and interpretation), while the JSON file defines the what (the inputs and the quality gates).</p>



<p class="wp-block-paragraph">Each test case is a self-contained JSON object that acts as a comprehensive contract for a specific chat interaction.</p>



<p class="wp-block-paragraph"><strong>Pros and scalability of such approach:</strong></p>



<ul class="wp-block-list">
<li>Zero-code onboarding: The primary advantage is accessibility. Business analysts, product managers, or junior QA engineers can write, modify, and review test cases without needing to understand WebSockets, Python, or Sentence Transformers.They just update the JSON.</li>



<li>Infinite horizontal scalability: Because the runner iterates through a standard JSON array, scaling the test suite from 10 cases to 10,000 cases requires zero architectural changes to the underlying Python code.</li>



<li>Version control friendly: JSON files diff beautifully in Git. We can track exactly when a <em>synonym </em>was added or when an <em>expected_answer</em> was updated to reflect a new product feature.</li>
</ul>



<h3 class="wp-block-heading">Test runner and reporting</h3>



<p class="wp-block-paragraph">We built a custom CLI runner that parses the `test_cases.json` file and executes the suite.</p>



<p class="wp-block-paragraph">To aid in debugging, we utilized `colorama` and regular expressions to strip out HTML tags and dynamically highlight detected keywords and synonyms in bright green directly in the terminal output. This allows QA engineers to visually verify why a test passed or failed at a glance.</p>



<p class="wp-block-paragraph">Finally, execution metrics (Test ID, Pass/Fail status, and response duration in seconds) are continuously appended to a results log file, allowing us to track performance latency and regression metrics over time.</p>



<h2 class="wp-block-heading"><strong>Results</strong></h2>



<p class="wp-block-paragraph"><a href="/services/qa-services/ai-testing-services/">Testing AI-powered systems</a> requires thinking beyond traditional binary assertions.</p>



<p class="wp-block-paragraph">Deploying this custom framework fundamentally transformed how our team approaches AI quality assurance. We moved away from the tedious manual testing that plagues many early-stage AI projects and replaced it with a more deterministic, data-driven pipeline.</p>



<p class="wp-block-paragraph">By combining strict keyword validation with semantic evaluation, we achieved a safety net that is both flexible and rigorous. This is the foundation for gathering hard metrics, such as latency, similarity scores, and hallucination catch-rates.</p>



<h3 class="wp-block-heading">What&#8217;s next?&nbsp;</h3>



<p class="wp-block-paragraph">While the current architecture handles single-turn queries beautifully, the next frontier is stateful, multi-turn conversations. We can evolve the framework to work in long contextual states, evaluating how well the bot remembers facts established three or four messages prior. Furthermore, we are looking into integrating dynamic LLM-as-a-Judge mechanisms, where a secondary model acts as the final arbiter for chatbot responses.</p>



<p class="wp-block-paragraph">The system also can be extended to load and concurrency testing. By parallelizing the test suite across multiple independent chat sessions, we can simulate real-world usage patterns and evaluate system behavior under concurrent requests. This enables measurement of performance characteristics such as response latency, throughput, and stability.</p>



<p class="wp-block-paragraph">Testing AI requires discarding the comfort of absolute determinism. By building frameworks that are as intelligent and adaptable as the systems they evaluate, our QA can stop playing catch-up and start leading the charge in building reliable AI products.</p>



<p class="wp-block-paragraph"><strong>Technologies used:</strong> Python, WebSockets, SignalR, PyTorch, Sentence Transformers (NLP), Scikit-learn, JSON, Regex.</p>



<div id="video-content" class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Testing an LLM Chatbot in an MCP System" width="500" height="281" class="lazyload"
                            data-src="https://www.youtube.com/embed/sC8T5xldqfw?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope;  web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
</div></div>




 
    
    <div class="editor-media-text editor-media-text_style_1 editor-media-text_index_4">
        
<div class="editor-media-text__split">
    
                
        <div class="editor-media-text__left">
            <div class="editor-media-text__image">
                                
                                    <img fetchpriority="high" decoding="async" class="editor-media-text__img" src="https://sam-solutions.com/wp-content/uploads/cta-Nesterenko.png"
                                                                              
                            srcset="https://sam-solutions.com/wp-content/uploads/cta-Nesterenko.png 1x, https://sam-solutions.com/wp-content/uploads/cta-Nesterenko@2x.png 2x"
                                                  alt="Nesterenko QA Chief"
                         width="200" height="292">
                        
            </div>     
        </div>   
        
    <div class="editor-media-text__right">
                                      
            <div class="editor-media-text__title">
                <div class="h4">Need help with AI testing?</div>            </div>
                
                            
            <div class="editor-media-text__descr">
                <div class="wysiwyg-editor"><p>Testing LLM-based systems requires more than traditional QA approaches. A structured validation strategy can help you detect hallucinations and improve response reliability in production AI applications.</p>
<p><b>Siarhei Nestsiarenka, Chief QA</b></p>
</div>
            </div>
                
                            
            <div class="editor-media-text__event">
                                    <div class="editor-media-text__button button button_style_1">
                        <a class="button__inner" href="https://sam-solutions.com/contacts/">                                                            <div class="button__name">Let’s talk about your project</div>
                                                        <div class="button__icon">
                                <svg class="button__icon-svg" xmlns="http://www.w3.org/2000/svg" width="32" height="12" viewBox="0 0 32 12" fill="none">
                                    <path d="M30.9834 5.50256C31.2581 5.77731 31.2581 6.22278 30.9834 6.49753L26.5062 10.9747C26.2315 11.2494 25.786 11.2494 25.5113 10.9747C25.2365 10.6999 25.2365 10.2545 25.5113 9.97977L29.491 6.00004L25.5113 2.02032C25.2365 1.74558 25.2365 1.30014 25.5113 1.02539C25.786 0.750651 26.2315 0.750651 26.5062 1.02539L30.9834 5.50256ZM0 5.29652H30.4859V6.70357H1.09781e-07L0 5.29652Z" fill="#FCFCFC"/>
                                </svg>
                            </div>
                        </a>                    </div>
                            </div>
            </div>
    
</div>    </div>
    
]]></content:encoded>
					
					<wfw:commentRss>https://sam-solutions.com/blog/llm-chatbot-testing/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://www.youtube.com/watch?v=sC8T5xldqfw" medium="video">
			<media:player url="https://www.youtube.com/watch?v=sC8T5xldqfw" />
			<media:title type="plain">Testing an LLM Chatbot in an MCP System</media:title>
			<media:description type="html"><![CDATA[This video explores why testing an LLM chatbot in an MCP-based system requires a different QA mindset than testing traditional deterministic software. It explains how MCP orchestration, RAG pipelines, tool calls, WebSockets, and streaming responses create multiple layers where failures can occur. The article also shows how a custom Python-based test framework can validate chatbot output through must-have checks, must-not rules, and semantic similarity analysis. Special attention is given to hallucination prevention, configuration-dependent results, and the challenges of testing multi-turn conversations. For teams building AI-powered products, it offers a practical look at how structured QA can make LLM systems more reliable, measurable, and business-safe.]]></media:description>
			<media:thumbnail url="https://i.ytimg.com/vi/sC8T5xldqfw/maxresdefault.jpg" />
			<media:rating scheme="urn:simple">adult</media:rating>
		</media:content>
	</item>
		<item>
		<title>Upgrading from Umbraco 14 to 17: Step-by-Step Migration Guide</title>
		<link>https://sam-solutions.com/blog/umbraco-14-to-17-upgrade/</link>
					<comments>https://sam-solutions.com/blog/umbraco-14-to-17-upgrade/#respond</comments>
		
		<dc:creator><![CDATA[Natallia Sakovich]]></dc:creator>
		<pubDate>Tue, 31 Mar 2026 09:43:46 +0000</pubDate>
				<guid isPermaLink="false">https://sam-solutions.com/?post_type=article&#038;p=35961</guid>

					<description><![CDATA[(If you prefer video content, please watch the concise video summary of this article below) Once the most difficult step of the Umbraco 13 to 14 upgrade was completed, the remaining migration path, upgrading Umbraco 14 to 15, then to 16, and finally to Umbraco 17 turned out to be significantly smoother.&#160; The main focus [&#8230;]]]></description>
										<content:encoded><![CDATA[<span id="more-35961"></span>
<!--noteaser-->



<iframe style="margin: 0;" width="100%" height="115" scrolling="no" frameborder="no" allow="autoplay" title="Upgrading from Umbraco 14 to 17: Step-by-Step Migration Guide" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/soundcloud%253Atracks%253A2297327474&#038;color=%23ff5500&#038;auto_play=false&#038;hide_related=false&#038;show_comments=false&#038;show_user=false&#038;show_reposts=false&#038;show_teaser=false"></iframe>

<p style="font-size:14px;"><em>(If you prefer video content, please <a href="#video-content">watch the concise video summary</a> of this article below)</em></p>




 
    
    <div class="editor-content editor-content_style_1 editor-content_index_5">
        
    
    <div class="editor-content__descr">
        <div class="wysiwyg-editor"><h2>Key Takeaways</h2>
<ul>
<li><b>The upgrade from Umbraco 14 to 16 is significantly smoother than 13 to 14. </b>Once the major breaking changes are behind you, the remaining migration steps become more predictable and easier to manage.</li>
<li><b>Temporarily disabling Razor compilation helps unblock the upgrade.</b> This allows you to regenerate models and fix issues incrementally instead of resolving everything upfront.</li>
<li><b>Authentication and API changes require careful handling.</b> Updates such as switching to Microsoft Entra ID and changes in ContentService (Save vs Publish) must be addressed to ensure system stability.</li>
<li><b>Prepare early for future versions like Umbraco 17.</b> Upgrading tools, dependencies, and editors (e.g., TinyMCE to TipTap) in advance reduces complexity and makes future upgrades faster and smoother.</li>
</ul>
</div>
    </div>
    </div>
    



<p class="wp-block-paragraph">Once the most difficult step of the <a href="/blog/umbraco-13-to-14-upgrade/">Umbraco 13 to 14 upgrade</a> was completed, the remaining migration path, upgrading Umbraco 14 to 15, then to 16, and finally to Umbraco 17 turned out to be significantly smoother.&nbsp;</p>



<p class="wp-block-paragraph">The main focus of this article is the <a href="/services/portals-wcms/umbraco/">Umbraco</a> 14 to 16 upgrade, covering API changes, authentication updates, and editor migration. As a bonus, we also touch on our initial experience with Umbraco 17.</p>




 
    
    <div class="editor-list-cta editor-list-cta_style_1 editor-list-cta_index_6">
        
    <div class="editor-list-cta__items">
                                    
                    			    				<style>
    					.editor-list-cta_index_6 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-button {
            				            				
            				            				        							    background: linear-gradient(to right, #a067e8, #527eff);
    							            				    					}
    				</style>
    			    			
    			    			    				<style>
    					.editor-list-cta_index_6 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left {
    					    padding-left: 10px;
    					}
    					
    					.editor-list-cta_index_6 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					    left: 0;
    					    top: 50%;
    					    width: 3px;
    					    content: '';
    					    position: absolute;
    					    margin-left: -10px;
    					    height: calc(100% + 10px);
    					    transform: translateY(-50%);

            				            					    							    background: linear-gradient(45deg, #527eff, #a067e8);
    							            				    					}
    					
    					@media (max-width: 475px) {
    					    .editor-list-cta_index_6 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					        width: 2px;
                                margin-left: -3.5px;
    					    }
    					}
    				</style>
    			                
                    			    			        
    			
                    			
                                
                <div class="editor-list-cta__item editor-list-cta__item_index_1">
                    <div class="editor-list-cta__item-inner">
            		                		                                <div class="editor-list-cta__item-left">
                                                                                                    <div class="editor-list-cta__item-title"><p><strong>Streamline your content management and power up</strong> your digital marketing with SaM Solutions&#8217; Umbraco services.</p>
</div>
                                                	        </div>
            	        
            	                    	            <div class="editor-list-cta__item-right">
            	                                    	                                	                                	            <div class="editor-list-cta__item-event">
                    	                <div class="editor-list-cta__item-button button button_style_1">
                                            <a class="button__inner" href="/services/portals-wcms/umbraco/?utm_source=blog&#038;utm_medium=post_ID_35961&#038;utm_campaign=cta_post_content_4029">                                                <div class="button__name">View offer</div>
                                            </a>                                        </div>
                                    </div>    
                                                                        
            	            </div>
            	                	        </div>
                </div>
                        </div>
    </div>
    



<h2 class="wp-block-heading"><strong>Fixing Razor Compilation Issues</strong></h2>



<p class="wp-block-paragraph">We discovered a few helpful tricks while working with Razor Pages. One of them is to disable Razor Pages validation at the project level.</p>



<p class="wp-block-paragraph">This is especially useful when updating between versions. Otherwise, all models can become invalid, and some types may be lost. To fix the models, you’d have to comment them out, but then Razor Pages would break. And to fix Razor Pages, you’d need to fix the models first. It turns out to be a vicious circle.</p>



<p class="wp-block-paragraph">It is much easier to resolve this issue by temporarily disabling Razor Page compilation, which allows the project to run and regenerate the published models without fixing every Razor view immediately.</p>



<p class="wp-block-paragraph">With this approach, you don’t need to correct every broken reference in your pages just to start the application. This is especially useful when upgrading between versions where models change and many views become invalid at once.</p>



<p class="wp-block-paragraph">This can be done by modifying the .csproj file directly:</p>



<pre class="wp-block-code"><code>     &lt;PropertyGroup&gt;
      &lt;RazorCompileOnBuild&gt;false&lt;/RazorCompileOnBuild&gt;
        &lt;RazorCompileOnPublish&gt;false&lt;/RazorCompileOnPublish&gt;
     &lt;/PropertyGroup&gt;</code></pre>



<p class="wp-block-paragraph">After starting the project and regenerating the models, you can gradually fix the views and re-enable Razor compilation.</p>



<p class="wp-block-paragraph">After that, run the site locally and regenerate the models.</p>



<p class="wp-block-paragraph">This does not include the number of problems that need to be solved related to backward incompatible database content and runtime errors/unexpected behavior of previously working logic (Null suddenly starting to appear instead of data, etc.).&nbsp;</p>



<h2 class="wp-block-heading"><strong>Authentication Provider Changes</strong></h2>



<h3 class="wp-block-heading">Authentication provider updates</h3>



<p class="wp-block-paragraph">The way the back-office authentication provider is defined has also changed. We temporarily disabled it to stay focused on completing the upgrade, and only updated the authentication setup when we reached version 16.</p>



<p class="wp-block-paragraph">Until then, we simply commented out all authentication-related code and reverted to basic login authentication. Since this project used Azure AD (now Microsoft Entra ID), we eventually had to rewrite the integration.</p>



<h3 class="wp-block-heading">Custom authentication plugin</h3>



<p class="wp-block-paragraph">The final solution was to add a custom authentication plugin configured through a YML file. (You can move this step to the end — that’s when we handled it too.)</p>



<p class="wp-block-paragraph"><strong>Example: Description of our provider</strong></p>



<p class="wp-block-paragraph">We initially ran into issues where Umbraco wouldn’t recognize the plugin definition. After some trial and error, we found that maintaining a strict folder structure and file format was crucial.</p>



<p class="wp-block-paragraph">We followed this <a href="https://docs.umbraco.com/umbraco-cms/tutorials/add-microsoft-entra-id-authentication" target="_blank" rel="noreferrer noopener nofollow">official guide</a>:</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_7">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image-4.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image-4.webp 1x, https://sam-solutions.com/wp-content/uploads/image-4@2x.webp 2x"
                  
         alt="Umbraco 14 to 17 upgrade"
         width="800" height="548">
    </div>
    



<p class="wp-block-paragraph">The way the back-office authentication provider is defined has also changed.</p>



<p class="wp-block-paragraph">We temporarily disabled it to stay focused on completing the upgrade, and only updated the authentication setup when we reached version 16.</p>



<p class="wp-block-paragraph">Until then, we simply commented out all authentication-related code and reverted to basic login authentication.</p>



<p class="wp-block-paragraph">Since this project used Azure AD (now Microsoft Entra ID), we eventually had to rewrite the integration.</p>



<h2 class="wp-block-heading"><strong>Upgrading to Umbraco 15</strong></h2>



<p class="wp-block-paragraph">Compared with the previous step, the upgrade to version 15 was much smoother.</p>



<p class="wp-block-paragraph">First, update the NuGet packages to the latest version 15 of both Umbraco and uSync. After that, you’ll probably start seeing a series of errors.</p>



<p class="wp-block-paragraph">Next, comment out all the problematic “red” sections in the code and temporarily disable Razor Page compilation, as mentioned earlier, to regenerate the publish models. Once that’s done, uncomment the code and start fixing the remaining errors.</p>



<p class="wp-block-paragraph">Finally, remove the parsing logic we discussed earlier, which seemed to be a quirk specific to version 14.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_8">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image-8-3.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image-8-3.webp 1x, https://sam-solutions.com/wp-content/uploads/image-8@2x.webp 2x"
                  
         alt="Umbraco 14 to 17 migration"
         width="680" height="155">
    </div>
    



<p class="wp-block-paragraph">The types were restored in the published models. At this stage, we also started configuring our TipTap rich-text editor, but we won’t go into detail here, since this setup is highly project-specific. Here you can find the <a href="https://docs.umbraco.com/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor" target="_blank" rel="noreferrer noopener nofollow">official documentation</a>.</p>



<h2 class="wp-block-heading"><strong>Upgrading to Umbraco 16</strong></h2>



<p class="wp-block-paragraph">Update the NuGet packages to the latest version 16 of both Umbraco and uSync. A few errors are likely to show up.&nbsp;</p>



<p class="wp-block-paragraph">Then, repeat the publish models trick mentioned earlier to rebuild them and continue with the upgrade process.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_9">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image-9.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image-9.webp 1x, https://sam-solutions.com/wp-content/uploads/image-9@2x.webp 2x"
                  
         alt="Umbraco 17 upgrade guide"
         width="773" height="203">
    </div>
    



<h3 class="wp-block-heading">Content service changes in version 16</h3>



<p class="wp-block-paragraph">In this version, the content service removed the combined SaveAndPublish option and split it into two separate methods: Save and Publish. With these methods you also need to specify the culture for which the operation should be performed. This gives you the flexibility to perform several saves first and only publish at the very end, which can be very convenient in many scenarios (ours included).​</p>



<p class="wp-block-paragraph">If you haven’t already switched your editors from TinyMCE to TipTap, Umbraco 16 will effectively force you to do so, because the TinyMCE UI is no longer available in this version.</p>



<p class="wp-block-paragraph">This is also the point where we implemented our authentication provider as an Umbraco Plugin, using the approach described earlier for integrating with external authentication (in our case, Microsoft Entra ID).</p>



<h2 class="wp-block-heading"><strong>Lessons Learned</strong></h2>



<p class="wp-block-paragraph">After completing the migration, several key lessons became clear.</p>




 
    
    <div class="editor-list-step editor-list-step_style_1 editor-list-step_index_10">
        
    <div class="editor-list-step__items">
                                    <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><h3 class="h5">The hardest step is upgrade from 13 to 14</h3>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p><span style="font-weight: 400;">Most of the breaking changes occur at this stage, so version 14 turned out to be the most incompatible with the previous version among all the upgrades described in this article. During this transition we encountered several major changes, including:</span></p>
<ul>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">macros removed</span></li>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">nested types replaced</span></li>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">editor changes</span></li>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">authentication integration breaking and requiring updates</span></li>
</ul>
<p><span style="font-weight: 400;">Compared to the later upgrades (14 → 15 → 16), this step required significantly more effort and adjustments.</span></p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><h3 class="h5">Automate content migration</h3>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p><span style="font-weight: 400;">Writing a custom migration job saved enormous manual effort. Whenever possible, use existing migration tools, we relied on several of them during our upgrade. However, if no suitable tool is available, it is often worth building your own automation rather than updating thousands of content items manually.</span></p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><h3 class="h5">Use database checkpoints</h3>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p><span style="font-weight: 400;">Create database backups after each successful upgrade step. These intermediate checkpoints allow you to roll back to the previous working stage if something breaks, instead of restarting the entire migration from the very beginning. These are like save points in a game, they significantly reduce the risk and effort involved in a long upgrade process.</span></p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><h3 class="h5">Expect localization issues</h3>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p><span style="font-weight: 400;">Language codes and culture formats may change between versions. During our upgrade, we also discovered that one of the cultures used in our portal had become invalid in the newer Umbraco version. We are not sure how common this issue is, but it’s something to be aware of, as similar localization inconsistencies may appear during your upgrade process.</span></p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><h3 class="h5">Upgrade tools early</h3>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p><span style="font-weight: 400;">Moving to new editors and components early makes later upgrades easier. It’s also important not to postpone upgrades for several versions, as jumping across multiple releases at once increases risk. The larger the version gap, the more breaking changes accumulate, making the upgrade more time-consuming and increasing the chance of missing important migration steps.</span></p>
</div>
                        </div>
                                    </div>  
                        
    </div>
    </div>
    



<h2 class="wp-block-heading"><strong>Bonus: Upgrading to Umbraco 17 and .NET 10</strong></h2>



<h3 class="wp-block-heading">Step 1: Upgrade to .NET 10</h3>



<p class="wp-block-paragraph">The first step is to upgrade your project to .NET 10. You can find all breaking changes <a href="https://learn.microsoft.com/en-us/dotnet/core/compatibility/10" target="_blank" rel="noreferrer noopener nofollow">here</a>.&nbsp;</p>



<p class="wp-block-paragraph">At the very beginning, we also decided to migrate our solution file from <em>.sln</em> to <em>.slnx</em>, which significantly simplifies solution management. This format was introduced with <a href="https://devblogs.microsoft.com/dotnet/introducing-slnx-support-dotnet-cli/" target="_blank" rel="noreferrer noopener nofollow">.NET 9.2</a>.</p>



<p class="wp-block-paragraph">To migrate, simply run dotnet solution migrate:</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_11">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image7-1.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image7-1.webp 1x, https://sam-solutions.com/wp-content/uploads/image7-1@2x.webp 2x"
                  
         alt="Upgrading to Umbraco 17"
         width="632" height="88">
    </div>
    



<p class="wp-block-paragraph">Instead of a 50+ line .sln file full of GUID references, you get a much cleaner file with around 10 lines.</p>



<p class="wp-block-paragraph">At the same time, don’t forget to:</p>



<ul class="wp-block-list">
<li>update all <strong>CI/CD pipelines</strong> to use <em>.slnx</em></li>



<li>switch to <strong>.NET 10</strong> in your build environment</li>



<li>remove the old .sln file (after verifying everything works correctly)</li>
</ul>



<p class="wp-block-paragraph">After that, update all projects to:</p>



<ul class="wp-block-list">
<li><strong><a href="/services/technologies/dot_net/">.NET</a> 10</strong></li>



<li><a href="/services/technologies/c-sharp-development-services/"><strong>C#</strong></a><strong> language version 14.0</strong></li>
</ul>



<h3 class="wp-block-heading">Step 2: Update dependencies</h3>



<p class="wp-block-paragraph">Next, update all dependent NuGet packages to their latest <strong>10.0-compatible versions</strong>.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_12">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image4-1.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image4-1.webp 1x, https://sam-solutions.com/wp-content/uploads/image4-1@2x.webp 2x"
                  
         alt="Umbraco 17"
         width="722" height="187">
    </div>
    



<p class="wp-block-paragraph">One breaking change we encountered was related to <a href="https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/docs/migrating-to-v10.md" target="_blank" rel="noreferrer noopener nofollow">Swashbuckle.AspNetCore</a>.</p>



<h3 class="wp-block-heading">Step 3: Fix ICU runtime issue</h3>



<p class="wp-block-paragraph">After resolving .NET 10-related issues and attempting to run the project, we encountered the following error:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Failed to load app-local ICU: icuuc68.2.0.9.dll
   at System.Environment.FailFast(System.Runtime.CompilerServices.StackCrawlMarkHandle, System.String, System.Runtime.CompilerServices.ObjectHandleOnStack, System.String)
   at System.Environment.FailFast(System.Threading.StackCrawlMark ByRef, System.String, System.Exception, System.String)
   at System.Environment.FailFast(System.String)

</pre></div>


<p class="wp-block-paragraph">This is a <a href="https://github.com/umbraco/Umbraco-CMS/issues/14661" target="_blank" rel="noreferrer noopener nofollow">known issue</a> in Umbraco.</p>



<p class="wp-block-paragraph">It is likely related to incomplete support for .NET 10 in Umbraco. We fixed it by updating the ICU version in the <em>.csproj</em> file:</p>



<pre class="wp-block-code"><code> &lt;RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="68.2.0.9" /&gt;</code></pre>



<p class="wp-block-paragraph">replaced with:</p>



<pre class="wp-block-code"><code> &lt;RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="72.1.0.3" /&gt; </code></pre>



<p class="wp-block-paragraph">The version must match the installed ICU package.</p>



<h3 class="wp-block-heading">Step 4: Fix minor breaking changes</h3>



<p class="wp-block-paragraph">After this fix, we only had 3 build errors across 2 files.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_13">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image11-1.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image11-1.webp 1x, https://sam-solutions.com/wp-content/uploads/image11-1@2x.webp 2x"
                  
         alt="updating to Umbraco 17"
         width="597" height="63">
    </div>
    



<p class="wp-block-paragraph">Here we just need to replace this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
  var backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment)
</pre></div>


<p class="wp-block-paragraph">with this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
 var backOfficePath = hostingEnvironment.GetBackOfficePath()
</pre></div>



 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_14">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image5-1.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image5-1.webp 1x, https://sam-solutions.com/wp-content/uploads/image5-1@2x.webp 2x"
                  
         alt="Umbraco 17 upgrading guide"
         width="824" height="84">
    </div>
    



<h3 class="wp-block-heading">Step 5: Navigation API adjustment</h3>



<p class="wp-block-paragraph">Another change required adjusting navigation logic.</p>



<p class="wp-block-paragraph">Final version:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
               _navigationQueryService.TryGetRootKeys(out var rootKeys);
                var root = rootKeys
                    .Select(key =&gt; umbracoContextReference.UmbracoContext.Content.GetById(key))
                    .FirstOrDefault(x =&gt; x?.UrlSegment() == &quot;unrestricted-pages&quot;);
                return new Home(root, _publishedValueFallback);

</pre></div>


<h3 class="wp-block-heading">Step 6: Date handling changes (important)</h3>



<p class="wp-block-paragraph">Umbraco finally fixed an issue with dates stored in SQL that we faced as well, since we store and manage events for multiple locations and time zones. We had an issue in previous versions where Umbraco always tried to reconvert our stored dates, even though we had already stored them in a specific time zone, but Umbraco still returned them as time zone specific datetimes marked as UTC, which created a lot of confusion.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_15">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image3-1.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image3-1.webp 1x, https://sam-solutions.com/wp-content/uploads/image3-1@2x.webp 2x"
                  
         alt="Umbraco 17 migration guide"
         width="704" height="150">
    </div>
    



<p class="wp-block-paragraph">That’s why we created a helper method like this to reset UTC kind.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_16">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image8-1.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image8-1.webp 1x, https://sam-solutions.com/wp-content/uploads/image8-1@2x.webp 2x"
                  
         alt="How migrate to Umbraco 17"
         width="824" height="140">
    </div>
    



<p class="wp-block-paragraph">But after we migrated to Umbraco 17 we changed this helper just to:</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_17">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image6-1.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image6-1.webp 1x, https://sam-solutions.com/wp-content/uploads/image6-1@2x.webp 2x"
                  
         alt="Umbraco 17 upgrading"
         width="659" height="110">
    </div>
    



<p class="wp-block-paragraph">Since now all dates are returned as <em>Unspecified</em>, we can convert them to any time zone we need without any recalculations, even if they were created before the Umbraco 17 update!</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_18">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image2-1.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image2-1.webp 1x, https://sam-solutions.com/wp-content/uploads/image2-1@2x.webp 2x"
                  
         alt="practical guide to Umbraco 17 migration"
         width="382" height="420">
    </div>
    



<h3 class="wp-block-heading">Step 7: Run Umbraco upgrade</h3>



<p class="wp-block-paragraph">After launching the application, you will see the standard Umbraco upgrade installation page.</p>



<p class="wp-block-paragraph">Click <em>Continue</em>, the process is straightforward and should not cause issues.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_19">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image10-1.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image10-1.webp 1x, https://sam-solutions.com/wp-content/uploads/image10-1@2x.webp 2x"
                  
         alt="upgrade from Umbraco 14 to 17"
         width="824" height="669">
    </div>
    



<h3 class="wp-block-heading">Step 8: Final cleanup</h3>



<p class="wp-block-paragraph">After the upgrade, don’t forget to regenerate published models and rework obsolete methods, the amount of which is definitely increased in Umbraco 17. We encountered around 60+ warnings after the first build. This is important because many of these obsolete methods will be removed entirely in Umbraco 18.</p>



<h2 class="wp-block-heading"><strong>Final Thoughts</strong></h2>



<p class="wp-block-paragraph">Upgrading to Umbraco 16 and then to version 17 turned out to be much faster and smoother than all previous ones. Compared to the complexity of the Umbraco 13 to 14 migration, this step required significantly less effort.</p>



<p class="wp-block-paragraph">Another key takeaway is: don’t delay upgrades. The longer you wait, the more breaking changes accumulate, making the eventual jump across multiple versions far more painful and time-consuming. It’s always better to upgrade incrementally rather than face a large, complex migration later.</p>



<p class="wp-block-paragraph">And if you’ve already skipped several versions, don’t worry. The team at SaM Solutions is ready to take on that complexity and handle the upgrade for you.</p>



<div id="video-content" class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Upgrading from Umbraco 14 to 17: Step-by-Step Migration Guide" width="500" height="281" class="lazyload"
                            data-src="https://www.youtube.com/embed/2KYPF8LF5fw?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope;  web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
</div></div>




 
    
    <div class="editor-media-text editor-media-text_style_1 editor-media-text_index_20">
        
<div class="editor-media-text__split">
    
                
        <div class="editor-media-text__left">
            <div class="editor-media-text__image">
                                
                                    <img decoding="async" class="editor-media-text__img" src="https://sam-solutions.com/wp-content/uploads/cta-Birkas.webp"
                                                                              
                            srcset="https://sam-solutions.com/wp-content/uploads/cta-Birkas.webp 1x, https://sam-solutions.com/wp-content/uploads/cta-Birkas@2x.webp 2x"
                                                  alt=""
                         width="200" height="292">
                        
            </div>     
        </div>   
        
    <div class="editor-media-text__right">
                                      
            <div class="editor-media-text__title">
                <div class="h4">Planning an Umbraco upgrade?</div>            </div>
                
                            
            <div class="editor-media-text__descr">
                <div class="wysiwyg-editor"><p><span style="font-weight: 400;">If you’re preparing to move to Umbraco 17, a well-structured migration strategy can save you weeks of rework and prevent critical issues with content and functionality.</span></p>
<p><b>Vadim Birkos, Senior Full-Stack .NET Developer, AI Enthusiast</b></p>
</div>
            </div>
                
                            
            <div class="editor-media-text__event">
                                    <div class="editor-media-text__button button button_style_1">
                        <a class="button__inner" href="https://sam-solutions.com/contacts/">                                                            <div class="button__name">Let’s talk about your project</div>
                                                        <div class="button__icon">
                                <svg class="button__icon-svg" xmlns="http://www.w3.org/2000/svg" width="32" height="12" viewBox="0 0 32 12" fill="none">
                                    <path d="M30.9834 5.50256C31.2581 5.77731 31.2581 6.22278 30.9834 6.49753L26.5062 10.9747C26.2315 11.2494 25.786 11.2494 25.5113 10.9747C25.2365 10.6999 25.2365 10.2545 25.5113 9.97977L29.491 6.00004L25.5113 2.02032C25.2365 1.74558 25.2365 1.30014 25.5113 1.02539C25.786 0.750651 26.2315 0.750651 26.5062 1.02539L30.9834 5.50256ZM0 5.29652H30.4859V6.70357H1.09781e-07L0 5.29652Z" fill="#FCFCFC"/>
                                </svg>
                            </div>
                        </a>                    </div>
                            </div>
            </div>
    
</div>    </div>
    
]]></content:encoded>
					
					<wfw:commentRss>https://sam-solutions.com/blog/umbraco-14-to-17-upgrade/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://www.youtube.com/watch?v=K2KYPF8LF5fw" medium="video">
			<media:player url="https://www.youtube.com/watch?v=K2KYPF8LF5fw" />
			<media:title type="plain">Upgrading from Umbraco 14 to 17: Step-by-Step Migration Guide</media:title>
			<media:description type="html"><![CDATA[This video presentation by SaM‑Solutions describes a practical, step‑by‑step migration path from Umbraco 14 to Umbraco 16, with a brief look ahead to Umbraco 17 and the .NET 10 upgrade. The authors explain that the jump from Umbraco 13 to 14 is the most disruptive, while upgrades from 14 to 15, 16, and onward become progressively smoother as major breaking changes are already behind you. The main message is that incremental, well‑prepared upgrades are far less painful than skipping multiple versions at once, and that investing early in tooling, automation, and editor modernization makes future Umbraco migrations significantly easier.]]></media:description>
			<media:thumbnail url="https://i.ytimg.com/vi/2KYPF8LF5fw/maxresdefault.jpg" />
			<media:rating scheme="urn:simple">adult</media:rating>
		</media:content>
	</item>
		<item>
		<title>Upgrading from Umbraco 13 to 14: Our Journey from Build Errors to Breakthroughs</title>
		<link>https://sam-solutions.com/blog/umbraco-13-to-14-upgrade/</link>
					<comments>https://sam-solutions.com/blog/umbraco-13-to-14-upgrade/#respond</comments>
		
		<dc:creator><![CDATA[Natallia Sakovich]]></dc:creator>
		<pubDate>Thu, 19 Mar 2026 16:31:18 +0000</pubDate>
				<guid isPermaLink="false">https://sam-solutions.com/?post_type=article&#038;p=35413</guid>

					<description><![CDATA[(If you prefer video content, please watch the concise video summary of this article below) Umbraco CMS is a widely used and rapidly evolving .NET-based content management system. At SaM Solutions, we also rely on Umbraco for our internal corporate portal. However, the platform’s fast pace of development has a downside: new versions frequently introduce [&#8230;]]]></description>
										<content:encoded><![CDATA[<span id="more-35413"></span>
<!--noteaser-->



<iframe style="margin: 0;" width="100%" height="115" scrolling="no" frameborder="no" allow="autoplay" title="Upgrading from Umbraco 13 to 14: Our Journey from Build Errors to Breakthroughs" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/soundcloud%253Atracks%253A2287329542&#038;color=%23ff5500&#038;auto_play=false&#038;hide_related=false&#038;show_comments=false&#038;show_user=false&#038;show_reposts=false&#038;show_teaser=false"></iframe>

<p style="font-size:14px;"><em>(If you prefer video content, please <a href="#video-content">watch the concise video summary</a> of this article below)</em></p>




 
    
    <div class="editor-content editor-content_style_1 editor-content_index_21">
        
    
    <div class="editor-content__descr">
        <div class="wysiwyg-editor"><h2>Key Takeaways</h2>
<ul>
<li><b>The biggest challenge is the Umbraco 13 to 14 jump. </b>Most breaking changes occur at this stage (macros removal, API changes, content structure updates). Once completed, further upgrades become significantly smoother.</li>
<li><b>Macros migration is the critical blocker.</b> Since macros are removed in Umbraco 14, converting them to blocks (often via custom migration jobs) is essential to preserve existing content.</li>
<li><b>Preparation and strategy save time. </b>Upgrading .NET in advance, creating backups, freezing content, and using a local-first migration approach help reduce risks and speed up the process.</li>
<li><strong>Incremental upgrades are more reliable than skipping versions.</strong> Even if targeting newer versions like Umbraco 17, you’ll likely need to handle the same core challenges introduced in version 14.</li>
</ul>
</div>
    </div>
    </div>
    



<p class="wp-block-paragraph">Umbraco CMS is a widely used and rapidly evolving .NET-based content management system. At SaM Solutions, we also rely on Umbraco for our internal corporate portal. However, the platform’s fast pace of development has a downside: new versions frequently introduce breaking changes, and upgrades are not always seamless.</p>



<p class="wp-block-paragraph">Our team has been working with <a href="/services/portals-wcms/umbraco/">Umbraco</a> since version 9. At the time we started planning the upgrade, our corporate portal was running on Umbraco 13, which is scheduled to reach its end of life on December 14, 2026. Many community members recommend skipping version 16 entirely (its EOL is June 12, 2026) and waiting for Umbraco 17, which has a longer support window until November 27, 2028.</p>



<p class="wp-block-paragraph">There is logic in that advice. However, our experience showed that the most difficult migration step occurs earlier, when moving from Umbraco 13 to 14. The following upgrades from version 14 to 15 and then to 16 are significantly smoother. In practice, anyone upgrading directly to version 17 will most likely still need to overcome the same obstacles.</p>



<p class="wp-block-paragraph">This article shares our real-world Umbraco 13 to 14 upgrade experience, including the problems we faced and how we solved them.</p>




 
    
    <div class="editor-list-cta editor-list-cta_style_1 editor-list-cta_index_22">
        
    <div class="editor-list-cta__items">
                                    
                    			    				<style>
    					.editor-list-cta_index_22 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-button {
            				            				
            				            				        							    background: linear-gradient(to right, #a067e8, #527eff);
    							            				    					}
    				</style>
    			    			
    			    			    				<style>
    					.editor-list-cta_index_22 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left {
    					    padding-left: 10px;
    					}
    					
    					.editor-list-cta_index_22 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					    left: 0;
    					    top: 50%;
    					    width: 3px;
    					    content: '';
    					    position: absolute;
    					    margin-left: -10px;
    					    height: calc(100% + 10px);
    					    transform: translateY(-50%);

            				            					    							    background: linear-gradient(45deg, #527eff, #a067e8);
    							            				    					}
    					
    					@media (max-width: 475px) {
    					    .editor-list-cta_index_22 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					        width: 2px;
                                margin-left: -3.5px;
    					    }
    					}
    				</style>
    			                
                    			    			        
    			
                    			
                                
                <div class="editor-list-cta__item editor-list-cta__item_index_1">
                    <div class="editor-list-cta__item-inner">
            		                		                                <div class="editor-list-cta__item-left">
                                                                                                    <div class="editor-list-cta__item-title"><p><strong>Request SaM Solutions&#8217; Umbraco implementation to</strong> speed up and optimize your content management workflows.</p>
</div>
                                                	        </div>
            	        
            	                    	            <div class="editor-list-cta__item-right">
            	                                    	                                	                                	            <div class="editor-list-cta__item-event">
                    	                <div class="editor-list-cta__item-button button button_style_1">
                                            <a class="button__inner" href="/services/portals-wcms/umbraco/?utm_source=blog&#038;utm_medium=post_ID_35413&#038;utm_campaign=cta_post_content_4028">                                                <div class="button__name">Our services</div>
                                            </a>                                        </div>
                                    </div>    
                                                                        
            	            </div>
            	                	        </div>
                </div>
                        </div>
    </div>
    



<h2 class="wp-block-heading"><strong>Initial System Architecture</strong></h2>



<p class="wp-block-paragraph">Before starting the upgrade, our portal had the following technical setup.</p>



<h3 class="wp-block-heading">Platform and framework</h3>



<ul class="wp-block-list">
<li><a href="/services/technologies/dot_net/">.NET</a> 8</li>



<li>Pages and components (partially migrated to Next.js with headless) but prod is still on Razor Pages</li>



<li>Hangfire for background job processing</li>
</ul>



<h3 class="wp-block-heading">Umbraco ecosystem</h3>



<ul class="wp-block-list">
<li>Umbraco CMS 13.8.1</li>



<li>uSync for content synchronization</li>



<li>Examine Search</li>



<li>Azure AD authentication for the Umbraco backoffice and for all users of the Umraco portal, with automatic user creation in the portal</li>
</ul>



<h3 class="wp-block-heading">Background jobs</h3>



<p class="wp-block-paragraph">Our portal relied heavily on automated background jobs interacting with Umbraco content.</p>



<p class="wp-block-paragraph">We had roughly six jobs, including:</p>



<ul class="wp-block-list">
<li>Content validation jobs triggered on content updates that make some changes to it (checking fields, setting the author, etc.)</li>



<li>Jobs for content translation across cultures based on a cron schedule</li>



<li>Import jobs that fetch content from external services</li>
</ul>



<p class="wp-block-paragraph">One of the more complex jobs translated content between cultures based on a cron schedule. This functionality is described in more detail in another article about our <a href="/blog/internal-ai-deployment-for-seamless-content-translation/">internal AI-powered translation system</a>.&nbsp;</p>



<h2 class="wp-block-heading"><strong>It Should Be Noted</strong></h2>



<p class="wp-block-paragraph">If you try to upgrade to version 16 right away and run it, you will get something like this:</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_23">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image-1-1.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image-1-1.webp 1x, https://sam-solutions.com/wp-content/uploads/image-1-1@2x.webp 2x"
                  
         alt="Umbraco 13 to 14"
         width="824" height="231">
    </div>
    



<p class="wp-block-paragraph">That’s why we decided on the incremental migration, starting with upgrading Umbraco 13 to 14.&nbsp;</p>



<h2 class="wp-block-heading"><strong>Custom Macros that Blocked the Upgrade</strong></h2>



<p class="wp-block-paragraph">One of the biggest blockers when upgrading to Umbraco 14 was the removal of macros. The thing is that our content managers used several custom macros when creating news articles on the portal.</p>



<p class="wp-block-paragraph">The most important ones included:</p>



<ul class="wp-block-list">
<li><strong>ExternalIframeBlock</strong> for<strong> </strong>displaying external iframe content&nbsp;</li>



<li><strong>IframeMediaWrapper </strong>for<strong> </strong>displaying media inside an iframe based on a link</li>



<li><strong>ScaleImage </strong>for<strong> </strong>resizing images dynamically</li>



<li><strong>SliderImages </strong>— a<strong> </strong>custom image slider</li>
</ul>



<p class="wp-block-paragraph">Unfortunately, macros were completely removed in Umbraco 14 and replaced by blocks. To preserve existing content without rewriting thousands of articles manually, we decided to migrate the macros with the help of coding and turn them into blocks.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_24">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image-1.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image-1.webp 1x, https://sam-solutions.com/wp-content/uploads/image-1@2x.webp 2x"
                  
         alt="upgrading Umbraco 13 to 14"
         width="911" height="361">
    </div>
    



<p class="wp-block-paragraph"><em>Source: </em><a href="https://docs.umbraco.com/umbraco-cms/fundamentals/setup/upgrading/version-specific#umbraco-14" target="_blank" rel="noreferrer noopener nofollow"><em>Umbraco documentation</em></a></p>



<h2 class="wp-block-heading"><strong>Umbraco Migration Strategy</strong></h2>



<p class="wp-block-paragraph">To speed up the upgrade process and be able to deploy only the final version, we decided to perform the entire migration locally on a single machine.</p>



<p class="wp-block-paragraph">The workflow looked as follows:</p>



<ul class="wp-block-list">
<li><strong>Restoring the database from the production environment to a local machine. </strong>We created a local copy of the production database to ensure that the migration process would run against real content and real data structures. This helped us detect potential issues early, especially those related to content serialization, macros, and custom blocks.</li>



<li><strong>Introducing a code and content freeze on production until the final deployment. </strong>Because the upgrade process was large and involved multiple breaking changes, we temporarily froze both code changes and content updates in the production environment. In theory, content editing could still continue during the migration if content authors were willing to manually repeat those changes after the upgrade. However, to avoid inconsistencies and additional overhead, we chose to freeze the content until the final deployment. We know this is an unusual approach, but in our circumstances we could afford it.</li>



<li><strong>Performing the entire migration locally and restoring the database after the upgrade was complete. </strong>Once all migration steps were finished and the upgraded application worked correctly locally, we manually restored the updated database to the target environment and deployed the final version of the portal.</li>
</ul>



<h3 class="wp-block-heading">Alternative scenario</h3>



<p class="wp-block-paragraph">In a more traditional scenario, the migration could have been performed incrementally using intermediate environments (for example, development → staging → pre-production) and deploying each intermediate upgrade step along the way.</p>



<p class="wp-block-paragraph">However, we intentionally chose a local-first migration approach to accelerate the process. Many issues were easier to fix only after reaching the target version (Umbraco 16), so repeatedly deploying intermediate versions would have slowed down the overall workflow.</p>



<p class="wp-block-paragraph">Another factor that made this approach feasible was our project setup. Technical Product Owner was actively involved in the upgrade process and was able to execute the migration steps directly on their local machine. This significantly simplified coordination and allowed us to move through the upgrade stages faster than a typical multi-environment deployment pipeline would allow.</p>



<p class="wp-block-paragraph">This approach works best if staging and production databases are identical and if you can follow the same steps like we did in terms of security policy (when Technical Product Owner is involved).</p>



<p class="wp-block-paragraph">After restoring the database in staging, some environment-specific configurations (like user permissions) had to be reconfigured.</p>



<h2 class="wp-block-heading"><strong>Branching Strategy for the Umbraco Upgrade</strong></h2>



<p class="wp-block-paragraph">During the migration we created <strong>separate Git branches for each final Umbraco version</strong>:</p>



<ul class="wp-block-list">
<li>upgrade-to-14</li>



<li>upgrade-to-15</li>



<li>upgrade-to-16</li>
</ul>



<p class="wp-block-paragraph">This strategy turned out to be extremely helpful. Whenever something broke (which happened quite often) we could simply:</p>



<ul class="wp-block-list">
<li>restore a fresh Umbraco 13 database backup</li>



<li>rerun the upgrade path</li>



<li>quickly compare results</li>
</ul>



<p class="wp-block-paragraph">We also strongly recommend creating database backups after each successful intermediate upgrade.</p>



<h2 class="wp-block-heading"><strong>Preparing the Upgrade</strong></h2>



<p class="wp-block-paragraph">Before upgrading Umbraco itself, we completed several preparatory steps.</p>



<p class="wp-block-paragraph">At the very beginning, we recommend doing everything that can be done in advance. In our case, that meant writing a migration job, testing it, and updating to .NET 9. At the time of our migration from Umbraco 13, version 16 was the latest available, and it required .NET 9. This way, when you first upgrade to version 14 and encounter a bunch of errors, you can at least rule out those related to the .NET version.</p>



<p class="wp-block-paragraph">The upgrade itself went fairly smoothly and, in most cases, was resolved by simply updating NuGet packages, though it definitely didn’t work perfectly on the first try.</p>



<p class="wp-block-paragraph">Next, we upgraded Umbraco to 13.10 and uSync to 13.3, because that’s when blocks were introduced, the very ones we needed to migrate our macros to.&nbsp;</p>



<h3 class="wp-block-heading">Migrating macros to blocks in Umbraco</h3>



<p class="wp-block-paragraph">After that, we updated the existing content and added a draft version of the blocks we wanted to migrate to (apparently, we copied the macro logic with a few small modifications).</p>



<p class="wp-block-paragraph">Then we started developing the job for migrating macros to blocks, something we had to write ourselves, and it turned out to be one of the most important parts of the migration process.</p>



<p class="wp-block-paragraph">Check out <a href="https://gist.github.com/VadimBirkos/9bd081ae341ce1d9c90ff37300e3c472" target="_blank" rel="noreferrer noopener nofollow">our version of the job</a>, it can serve as a good starting point, though in your case you might need to cover more conditions.</p>



<p class="wp-block-paragraph"><strong>Details of this migration job:</strong></p>



<p class="wp-block-paragraph">In the end, there weren’t any major difficulties (though that’s after we figured everything out). During the process, we had to understand how Umbraco stores its data internally to know how to properly convert macros into blocks.</p>



<p class="wp-block-paragraph">Essentially, our task was to replace each macro in the content with a corresponding block. For example:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
News text  
MACRO_WITH_IMAGE  
More news text

</pre></div>


<p class="wp-block-paragraph">What we did:</p>



<ol class="wp-block-list">
<li>Scanned content for macros using regular expressions</li>



<li>Identified macro occurrences</li>



<li>Created corresponding block components</li>



<li>Replaced macro markup with block references</li>
</ol>



<p class="wp-block-paragraph">For creating new data types, we could use built-in Umbraco types, which made the work easier. We just had to be careful to follow the correct structure. The job also required that all the necessary custom blocks were already created at the site level to enable conversion.</p>



<p class="wp-block-paragraph">Our macros were fairly simple, displaying an image, embedding a video in an iframe, or showing multiple images. One advantage of migrating to a newer Umbraco version was that some of these custom blocks became unnecessary, since the new Rich Text Editor already included built-in components for them.</p>



<p class="wp-block-paragraph">The migration itself was needed mainly to preserve old content.</p>



<p class="wp-block-paragraph">After that, we ran the migration job. Once it was completed, we reviewed the list of pages that had contained macros and tested them to ensure everything worked correctly. If all looked good, we treated that as a successful upgrade checkpoint.</p>



<h3 class="wp-block-heading">Migration of Nested Content to BlockList and Grid to BlockGrid</h3>



<p class="wp-block-paragraph">The next step was to install the package <a href="https://github.com/Jumoo/uSyncMigrations" target="_blank" rel="noreferrer noopener nofollow">uSyncMigrations</a> in the local environment via NuGet.</p>



<p class="wp-block-paragraph">Next, we followed this sequence of steps:</p>



<ul class="wp-block-list">
<li>Go to Settings → uSync → Everything → Clean Export.</li>
</ul>



<p class="wp-block-paragraph">This helps avoid situations where uSync files and the current database version are out of sync.</p>



<ul class="wp-block-list">
<li>Then go to Settings → uSync Migrations → Convert Site and select the migration plan “Nested to BlockList and Grid to Block&#8230;”.</li>
</ul>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_25">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image-2.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image-2.webp 1x, https://sam-solutions.com/wp-content/uploads/image-2@2x.webp 2x"
                  
         alt="Umbraco 13 to Umbraco 14"
         width="765" height="250">
    </div>
    



<p class="wp-block-paragraph">This starts the migration process itself. It runs entirely based on uSync files — it takes the existing files and converts them to the new format.</p>



<p class="wp-block-paragraph">Once the migration finishes, perform an Import to apply the changes to the database. In our case, the process generated about <strong>4,000 files</strong>.</p>



<p class="wp-block-paragraph">After that, go to uSync → Everything → to import all migrated content into DB (BlockLists and BlockGrid).</p>



<h2 class="wp-block-heading"><strong>Upgrading to Umbraco 14</strong></h2>



<p class="wp-block-paragraph">To update the Umbraco NuGet package to version 14, you first need to temporarily disable the uSync package, otherwise, the main Umbraco package would not upgrade. Or change the package version manually in all <em>csproj</em> files or Directory.Packages.props in case you use CentralPackageManagement.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_26">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image-3.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image-3.webp 1x, https://sam-solutions.com/wp-content/uploads/image-3@2x.webp 2x"
                  
         alt="Umbraco 13 to Umbraco 14"
         width="771" height="196">
    </div>
    



<p class="wp-block-paragraph">After the update, reinstall version 14 of uSync and start addressing any errors to get the project running again.</p>



<p class="wp-block-paragraph">You also need to remove @inject Smidge.SmidgeHelper SmidgeHelper from _ViewImports.&nbsp;</p>



<p class="wp-block-paragraph">@await Umbraco.RenderMacroAsync(macroAlias, parameters)</p>



<h2 class="wp-block-heading"><strong>What Broke During the Umbraco 13 to 14 Upgrade</strong></h2>



<h3 class="wp-block-heading">Notification API changes (SendingContentNotification removed)</h3>



<p class="wp-block-paragraph">SendingContentNotification no longer exists, which means all related handlers have to be rewritten. We decided to use the newer <a href="https://apidocs.umbraco.com/v10/csharp/api/Umbraco.Cms.Core.Notifications.ContentSavingNotification.html" target="_blank" rel="noreferrer noopener nofollow">ContentSavingNotification</a> approach and were able to refactor our handlers accordingly.</p>



<p class="wp-block-paragraph">The main difference is that handler logic is now culture-dependent, and the structure of some objects has changed slightly.</p>



<p class="wp-block-paragraph">Example:</p>



<p class="wp-block-paragraph"><strong>V13</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
public class SomeMagicContentNotificationHandler(UmbracoHelper umbracoHelper)
    : INotificationHandler&amp;lt;SendingContentNotification&gt; 

  foreach (var variant in notification.Content.Variants)
{
.....
}

</pre></div>


<p class="wp-block-paragraph"><strong>Final version (v16)</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
public class SomeMagicContentNotificationHandler(
	IAppoinmentTimeZoneService appoinmentTimeZoneService,
	UmbracoHelper umbracoHelper)
	: INotificationHandler&amp;lt;ContentSavingNotification&gt;
  foreach (var content in notification.SavedEntities)
   {
   	if (!ValidationIfItemTemplateIsDesired(content))
       	continue;
	foreach (var culture in content.AvailableCultures)
 	{
.....
}
}
</pre></div>


<p class="wp-block-paragraph">In reality, there weren’t many changes but they still needed to be made.</p>



<h3 class="wp-block-heading">Incompatible classes after migration</h3>



<p class="wp-block-paragraph">We also removed our MacrosMigrationJob, since its mission was complete. It was only needed for the migration from version 13 to 14. Once we moved to version 14, several incompatible classes appeared that would have prevented the project from building.</p>



<h3 class="wp-block-heading">Label type changes in published models</h3>



<p class="wp-block-paragraph">Next, there was an issue with labels. We had Umbraco.Label types, and when we migrated to version 14, our published models changed the types to string. Although they should have been int and DateTime.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_27">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image-5.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image-5.webp 1x, https://sam-solutions.com/wp-content/uploads/image-5@2x.webp 2x"
                  
         alt="Umbraco upgrade"
         width="774" height="361">
    </div>
    



<p class="wp-block-paragraph">We handled the parsing manually but that’s actually unnecessary. In later Umbraco versions, this functionality will start working correctly again, and the models will return the proper type. It’s easier to just comment it out temporarily until you upgrade to the final version.</p>



<p class="wp-block-paragraph">In the future, Umbraco will allow this to be configured directly at the platform level, so you’ll be able to specify a custom Label type if it differs from string.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_28">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image-6.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image-6.webp 1x, https://sam-solutions.com/wp-content/uploads/image-6@2x.webp 2x"
                  
         alt="upgrade Umbraco 13 to Umbraco 14"
         width="770" height="319">
    </div>
    



<h3 class="wp-block-heading">Rich Text Editor migration (TinyMCE → TipTap)</h3>



<p class="wp-block-paragraph">During the upgrade to version 14, we also switched our editors from TinyMCE to TipTap.</p>



<p class="wp-block-paragraph">This change didn’t cause any issues, but it made future updates easier, since in later versions, TinyMCE was removed entirely.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_29">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image-10.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image-10.webp 1x, https://sam-solutions.com/wp-content/uploads/image-10@2x.webp 2x"
                  
         alt="upgrading Umbraco"
         width="846" height="399">
    </div>
    



<p class="wp-block-paragraph"><em>Source: </em><a href="https://docs.umbraco.com/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor" target="_blank" rel="noreferrer noopener nofollow"><em>Umbraco documentation</em></a></p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_30">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/image-7.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/image-7.webp 1x, https://sam-solutions.com/wp-content/uploads/image-7@2x.webp 2x"
                  
         alt="Umbraco 13 to 14"
         width="600" height="120">
    </div>
    



<h3 class="wp-block-heading">Content serialization issues after macro migration</h3>



<p class="wp-block-paragraph">Next, we needed to create another migration job. You can find our version <a href="https://gist.github.com/VadimBirkos/1cc70fdedcd91bd4e5f3e8d6d4aa5118" target="_blank" rel="noreferrer noopener nofollow">here</a>. In version 13, when we migrated macros to blocks, the properties were serialized in uppercase, which isn’t compatible with what’s expected in version 14 and later. To fix this, we wrote a small job that re-serializes all the created content where these macros or blocks were used.</p>



<p class="wp-block-paragraph">This step is only necessary if you previously used custom macros. If not, Umbraco will handle everything automatically during the update.</p>



<h2 class="wp-block-heading"><strong>To Sum Up</strong></h2>



<p class="wp-block-paragraph">From our experience, this was the most difficult part of the entire Umbraco upgrade process. The transition from Umbraco 13 to 14 introduced the majority of breaking changes, and once we successfully passed this stage, the rest of the migration (14 → 15 → 16 → 17) became significantly faster and more predictable.</p>



<p class="wp-block-paragraph">The main pain points were migrating macros to blocks and migrating nested types, which in our case also required changes in how we work with models at the code level (we will discuss it in more detail in the following article <a href="/blog/umbraco-14-to-17-upgrade/">Upgrading from Umbraco 14 to 17</a>).</p>



<p class="wp-block-paragraph">It is also worth mentioning that during the upgrade we intentionally commented out certain parts of the code to unblock the process and move forward. Many of these fixes were completed only after reaching the final version. So if you don’t see a specific issue or fix described here, it most likely appears in the second part of this guide.</p>



<div id="video-content" class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Upgrading from Umbraco 13 to 14: Our Journey from Build Errors to Breakthroughs" width="500" height="281" class="lazyload"
                            data-src="https://www.youtube.com/embed/-P4nPuM_CWY?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope;  web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
</div></div>




 
    
    <div class="editor-media-text editor-media-text_style_1 editor-media-text_index_31">
        
<div class="editor-media-text__split">
    
                
        <div class="editor-media-text__left">
            <div class="editor-media-text__image">
                                
                                    <img decoding="async" class="editor-media-text__img" src="https://sam-solutions.com/wp-content/uploads/cta-Birkas.webp"
                                                                              
                            srcset="https://sam-solutions.com/wp-content/uploads/cta-Birkas.webp 1x, https://sam-solutions.com/wp-content/uploads/cta-Birkas@2x.webp 2x"
                                                  alt=""
                         width="200" height="292">
                        
            </div>     
        </div>   
        
    <div class="editor-media-text__right">
                                      
            <div class="editor-media-text__title">
                <div class="h4">Planning an Umbraco upgrade?</div>            </div>
                
                            
            <div class="editor-media-text__descr">
                <div class="wysiwyg-editor"><p>If you&#8217;re preparing to move from Umbraco 13 to newer versions, a well-structured migration strategy can save you weeks of rework and prevent critical issues with content and functionality.</p>
<p><strong>Vadim Birkos, Senior Full-Stack .NET Developer, <span data-teams="true">AI Enthusiast </span></strong></p>
</div>
            </div>
                
                            
            <div class="editor-media-text__event">
                                    <div class="editor-media-text__button button button_style_1">
                        <a class="button__inner" href="https://sam-solutions.com/contacts/">                                                            <div class="button__name">Let’s talk about your project</div>
                                                        <div class="button__icon">
                                <svg class="button__icon-svg" xmlns="http://www.w3.org/2000/svg" width="32" height="12" viewBox="0 0 32 12" fill="none">
                                    <path d="M30.9834 5.50256C31.2581 5.77731 31.2581 6.22278 30.9834 6.49753L26.5062 10.9747C26.2315 11.2494 25.786 11.2494 25.5113 10.9747C25.2365 10.6999 25.2365 10.2545 25.5113 9.97977L29.491 6.00004L25.5113 2.02032C25.2365 1.74558 25.2365 1.30014 25.5113 1.02539C25.786 0.750651 26.2315 0.750651 26.5062 1.02539L30.9834 5.50256ZM0 5.29652H30.4859V6.70357H1.09781e-07L0 5.29652Z" fill="#FCFCFC"/>
                                </svg>
                            </div>
                        </a>                    </div>
                            </div>
            </div>
    
</div>    </div>
    
]]></content:encoded>
					
					<wfw:commentRss>https://sam-solutions.com/blog/umbraco-13-to-14-upgrade/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://www.youtube.com/watch?v=-P4nPuM_CWY" medium="video">
			<media:player url="https://www.youtube.com/watch?v=-P4nPuM_CWY" />
			<media:title type="plain">Upgrading from Umbraco 13 to 14: Our Journey from Build Errors to Breakthroughs</media:title>
			<media:description type="html"><![CDATA[Planning to upgrade to Umbraco 17? Don’t rush! Start with the hardest step first. Upgrading from Umbraco 13 to 14 is where things really break. Most breaking changes occur at this stage (macros removal, API changes, content structure updates). Once completed, further upgrades become significantly smoother. Preparation and strategy save time. Upgrading .NET in advance, creating backups, freezing content, and using a local-first migration approach help reduce risks and speed up the process. Incremental upgrades are more reliable than skipping versions. Even if targeting newer versions like Umbraco 17, you’ll likely need to handle the same core challenges introduced in version 14. In this video, SaM Solutions' developers share their real-life experience upgrading Umbraco 13 to 14.]]></media:description>
			<media:thumbnail url="https://i.ytimg.com/vi/-P4nPuM_CWY/maxresdefault.jpg" />
			<media:rating scheme="urn:simple">adult</media:rating>
		</media:content>
	</item>
		<item>
		<title>Developing an AI Assistant Prototype for Automated Lead Discovery and Qualification</title>
		<link>https://sam-solutions.com/blog/developing-ai-assistant-for-automated-lead-generation/</link>
					<comments>https://sam-solutions.com/blog/developing-ai-assistant-for-automated-lead-generation/#respond</comments>
		
		<dc:creator><![CDATA[Andrey Kopanev]]></dc:creator>
		<pubDate>Fri, 23 Jan 2026 09:33:16 +0000</pubDate>
				<guid isPermaLink="false">https://sam-solutions.com/?post_type=article&#038;p=32528</guid>

					<description><![CDATA[(If you prefer video content, please watch the concise video summary of this article below) Finding potential clients for software development services is rarely a straightforward task. In practice, it often means manually monitoring chats, forums, and social networks, identifying promising conversations, and then reaching out to people one by one with similar introductory messages. [&#8230;]]]></description>
										<content:encoded><![CDATA[<span id="more-32528"></span>
<!--noteaser-->



<iframe style="margin: 0;" width="100%" height="115" scrolling="no" frameborder="no" allow="autoplay" title="Developing an AI Assistant Prototype for Automated Lead Discovery and Qualification" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/soundcloud%253Atracks%253A2252708591&#038;color=%23ff5500&#038;auto_play=false&#038;hide_related=false&#038;show_comments=false&#038;show_user=false&#038;show_reposts=false&#038;show_teaser=false" rel="nofollow"></iframe><p style="font-size:14px;"><em>(If you prefer video content, please <a href="#video-content">watch the concise video summary</a> of this article below)</em></p>




 
    
    <div class="editor-content editor-content_style_1 editor-content_index_32">
        
    
    <div class="editor-content__descr">
        <div class="wysiwyg-editor"><h2><span style="font-weight: 400;">Key Takeaways</span></h2>
<ul>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Combining structured conversation scripts with LLM adaptability enables scalable, natural lead qualification without sounding robotic.</span></li>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">An AI assistant can accurately assess ICP fit by extracting and validating key business data from free-form conversations.</span></li>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Respecting user intent and recognizing disengagement is critical for ethical, effective AI-driven outreach.</span></li>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Production-ready AI lead generation requires not just models, but careful prompt design, validation logic, and cost control.</span></li>
</ul>
</div>
    </div>
    </div>
    



<p class="wp-block-paragraph">Finding potential clients for <a href="/">software development services</a> is rarely a straightforward task. In practice, it often means manually monitoring chats, forums, and social networks, identifying promising conversations, and then reaching out to people one by one with similar introductory messages. This process is time-consuming, repetitive, and difficult to scale.</p>



<p class="wp-block-paragraph">In this article, I explain how I built a working prototype of an AI-powered assistant <em>SaMio</em> that automates early-stage lead discovery and qualification for software development providers, while keeping conversations natural, respectful, and context-aware.</p>




 
    
    <div class="editor-list-cta editor-list-cta_style_1 editor-list-cta_index_33">
        
    <div class="editor-list-cta__items">
                                    
                    			    				<style>
    					.editor-list-cta_index_33 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-button {
            				            				
            				            				        							    background: linear-gradient(to right, #a067e8, #527eff);
    							            				    					}
    				</style>
    			    			
    			    			    				<style>
    					.editor-list-cta_index_33 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left {
    					    padding-left: 10px;
    					}
    					
    					.editor-list-cta_index_33 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					    left: 0;
    					    top: 50%;
    					    width: 3px;
    					    content: '';
    					    position: absolute;
    					    margin-left: -10px;
    					    height: calc(100% + 10px);
    					    transform: translateY(-50%);

            				            					    							    background: linear-gradient(45deg, #527eff, #a067e8);
    							            				    					}
    					
    					@media (max-width: 475px) {
    					    .editor-list-cta_index_33 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					        width: 2px;
                                margin-left: -3.5px;
    					    }
    					}
    				</style>
    			                
                    			    			        
    			
                    			
                                
                <div class="editor-list-cta__item editor-list-cta__item_index_1">
                    <div class="editor-list-cta__item-inner">
            		                		                                <div class="editor-list-cta__item-left">
                                                                                                    <div class="editor-list-cta__item-title"><p>Need expert guidance on designing and implementing AI solutions for your business?</p>
</div>
                                                	        </div>
            	        
            	                    	            <div class="editor-list-cta__item-right">
            	                                    	                                	                                	            <div class="editor-list-cta__item-event">
                    	                <div class="editor-list-cta__item-button button button_style_1">
                                            <a class="button__inner" href="/services/ai-consulting-services/?utm_source=blog&#038;utm_medium=post_ID_32528&#038;utm_campaign=cta_post_content_32540">                                                <div class="button__name">View offer</div>
                                            </a>                                        </div>
                                    </div>    
                                                                        
            	            </div>
            	                	        </div>
                </div>
                        </div>
    </div>
    



<h2 class="wp-block-heading"><strong>The Challenge: Manual Lead Discovery Doesn’t Scale</strong></h2>



<p class="wp-block-paragraph">The starting point was a very common real-life workflow.</p>



<p class="wp-block-paragraph">A company’s employee continuously monitors chats and social platforms to identify people who might be interested in software development services. Once such a person is found, the next step resembles cold outreach, only via modern messengers instead of phone calls. The outreach typically follows a script: greeting, a few qualifying questions, and then a proposal or a polite exit.</p>



<p class="wp-block-paragraph">The problems with this approach were obvious:</p>



<ul class="wp-block-list">
<li>The same introductory messages had to be written manually again and again.</li>



<li>Conversations rarely followed a perfectly linear script.</li>



<li>People could refuse, ignore messages, or shift the topic at any moment.</li>



<li>It was difficult to consistently evaluate whether a person actually fit the ideal customer profile (ICP).</li>
</ul>



<p class="wp-block-paragraph">The goal was to automate this process without turning it into a robotic spam machine and without annoying people.</p>



<h2 class="wp-block-heading"><strong>The Core Idea: Scripted Flow and LLM Adaptability</strong></h2>



<p class="wp-block-paragraph">Instead of trying to fully improvise conversations, I started with a structured script approach.</p>



<p class="wp-block-paragraph">At the core of <em>SaMio</em> lies a conversation flow definition, consisting of:</p>



<ul class="wp-block-list">
<li>Initial greeting messages</li>



<li>A sequence of qualifying questions</li>



<li>Final success or failure messages</li>
</ul>



<p class="wp-block-paragraph">Each question step has a specific validation goal, for example, identifying the industry, company size, or the person’s role in the organization.</p>



<p class="wp-block-paragraph">The number of questions is flexible and can be extended depending on business needs.</p>



<p class="wp-block-paragraph">However, a static script alone would never feel natural. Real conversations are messy. People answer indirectly, ask unrelated questions, or clarify earlier statements. This is where <a href="/services/ai-software-development/">AI</a> becomes essential.</p>



<h2 class="wp-block-heading"><strong>Making the Conversation Feel Human</strong></h2>



<p class="wp-block-paragraph">To make dialogues feel natural, I connected a large <a href="/blog/llm-architecture/">language model (LLM)</a> that adapts to the interlocutor’s communication style and context.</p>



<p class="wp-block-paragraph"><em>SaMio</em> does not simply send predefined messages. Instead, it:</p>



<ul class="wp-block-list">
<li>Selects appropriate variants from the script</li>



<li>Interprets free-form user responses</li>



<li>Extracts relevant information from those responses</li>



<li>Adjusts tone and pacing to match the conversation</li>
</ul>



<p class="wp-block-paragraph">If someone suddenly asks about the weather or shifts the topic, the assistant can respond naturally and then gently steer the conversation back when appropriate.</p>



<p class="wp-block-paragraph">At the same time, the assistant must recognize hard stops. If a person refuses to continue or clearly disengages, the assistant must respect that decision and end the dialogue without pushing further.</p>



<p class="wp-block-paragraph"><em>This balance — being adaptive without being intrusive — was one of the most important design goals.</em></p>



<h2 class="wp-block-heading"><strong>Qualification and Validation Logic</strong></h2>



<p class="wp-block-paragraph">As the conversation progresses, <em>SaMio</em> collects answers to the qualifying questions. Importantly:</p>



<ul class="wp-block-list">
<li>Answers are stored separately and can be updated if the user adds something later.</li>



<li>The system avoids asking the same question multiple times.</li>



<li>Each answer contributes to an overall assessment of how well the person fits the target ICP.</li>
</ul>



<p class="wp-block-paragraph">Once all relevant data is collected or the conversation naturally reaches a conclusion the assistant evaluates the result.</p>



<p class="wp-block-paragraph">If the potential client matches the ICP, the assistant selects one of several success messages. These are soft, non-pushy proposals, such as offering a short demo, sharing a guide, or showing real examples.</p>



<p class="wp-block-paragraph">If the person is not a fit or declines further discussion, the assistant selects a failure message, always polite, appreciative, and respectful.</p>



<h2 class="wp-block-heading"><strong>Post-Conversation Automation</strong></h2>



<p class="wp-block-paragraph"><em>SaMio</em> does not stop at messaging.</p>



<p class="wp-block-paragraph">After the conversation ends:</p>



<ul class="wp-block-list">
<li>A summary email with the results of the dialogue is automatically sent.</li>



<li>The full conversation history is stored in the database for later review.</li>



<li>Relevant data is synchronized with Google Sheets, keeping lead tracking up to date without manual input.</li>
</ul>



<p class="wp-block-paragraph">This ensures transparency, traceability, and easy handover to sales or marketing teams.</p>



<p class="wp-block-paragraph"><strong><em>Summary table example</em></strong></p>



<figure class="wp-block-table is-style-stripes"><table><thead><tr><th><strong>Name</strong></th><th><strong>User name</strong></th><th><strong>Position</strong></th><th><strong>Company</strong></th><th><strong>Size</strong></th><th><strong>Industry</strong></th><th><strong>Date</strong></th><th><strong>Last update</strong></th></tr></thead><tbody><tr><td>Yena Polrix</td><td>Ypolrix_po</td><td>User is a product owner</td><td>N/A</td><td>Approximately 300 people</td><td>The user works in the composable commerce sector</td><td>2025-11-22</td><td>2025-11-22</td></tr><tr><td>Luma Qentari</td><td>lqentari_77</td><td>User is a tech strategy lead</td><td>N/A</td><td>The company has nearly a thousand workers</td><td>Cloud automation</td><td>2025-10-19</td><td>2026-01-10</td></tr><tr><td>Nira Solven</td><td>nsolven_vp</td><td>User is a director of the company</td><td>N/A</td><td>Over 1200 people</td><td>Enterprise platform services</td><td>2025-12-10</td><td>2025-12-10</td></tr></tbody></table></figure>



<h2 class="wp-block-heading"><strong>Technical Decisions and Architecture</strong></h2>



<p class="wp-block-paragraph">Initially, I experimented with a local LLM (<em>Gemma</em>). While this approach seemed attractive from a cost and privacy perspective, it quickly revealed limitations.</p>



<p class="wp-block-paragraph">The model struggled to correctly interpret ambiguous responses. If a user replied off-topic but without rejecting the conversation the model often failed to adapt and continue meaningfully.</p>



<p class="wp-block-paragraph">As a result, I switched to <em>GPT-4o-mini</em>, hosted on Azure. This model provided significantly better conversational robustness and context handling.</p>



<p class="wp-block-paragraph">Another key challenge was controlling conversational drift. Since cloud-based models consume paid tokens, letting the assistant engage in long, irrelevant discussions was not an option. We had to carefully balance:</p>



<ul class="wp-block-list">
<li>Allowing natural small talk</li>



<li>Gently redirecting the conversation</li>



<li>Preventing unnecessary token consumption</li>



<li>Avoiding obvious “bot-like” behavior</li>
</ul>



<p class="wp-block-paragraph">Prompt engineering played a major role here, defining boundaries while preserving flexibility.</p>



<h3 class="wp-block-heading">Technology stack</h3>



<p class="wp-block-paragraph">The prototype was built using:</p>



<ul class="wp-block-list">
<li>Azure</li>



<li>Docker</li>



<li><a href="/services/technologies/dot_net/">.NET</a></li>



<li>PostgreSQL</li>
</ul>



<h2 class="wp-block-heading"><strong>The Result: A Working, Production-Ready Prototype</strong></h2>



<p class="wp-block-paragraph"><em>SaMio</em> is a fully functional prototype that:</p>



<ul class="wp-block-list">
<li>Conducts natural, adaptive conversations</li>



<li>Qualifies leads based on real responses</li>



<li>Respects user boundaries</li>



<li>Automates follow-ups and reporting</li>



<li>Stores and synchronizes data reliably</li>
</ul>



<p class="wp-block-paragraph">Most importantly, it demonstrates how LLM-based assistants can move beyond simple chatbots and become practical tools for real business workflows when combined with structured logic, validation rules, and thoughtful constraints.</p>




 
    
    <div class="editor-cta editor-cta_style_1 editor-cta_index_34">
        
<a class="editor-cta__inner" href="/blog/internal-ai-deployment-for-seamless-content-translation/">

                
    <div class="editor-cta__split">
                            
            <div class="editor-cta__left">
                <div class="editor-cta__image">
                                        
                                            <img decoding="async" class="editor-cta__img" src="https://sam-solutions.com/wp-content/uploads/icon-read-story.svg"
                                                          alt="Read how we implemented an internal AI-powered system for content translation."
                             width="68" height="76">
                            
                </div>     
            </div>   
                
                    <div class="editor-cta__right">
                <div class="editor-cta__title">
                    <div class="h5">Read how we implemented an internal AI-powered system for content translation.</div>
                </div>
            </div>
            </div>
    
    <div class="editor-cta__event">
        <div class="editor-cta__button">
            <span></span>
            <span></span>
            <span></span>
        </div>    
    </div>
    
</a>    </div>
    



<h2 class="wp-block-heading"><strong>Final Thoughts</strong></h2>



<p class="wp-block-paragraph">This project was a strong reminder that successful AI systems are rarely “pure AI.” The real value emerges at the intersection of scripted business logic, human communication patterns, and carefully controlled language models.</p>



<p class="wp-block-paragraph">For software development providers, this approach opens new possibilities for scalable, respectful lead generation without sacrificing authenticity or trust.</p>



<p class="wp-block-paragraph">If you’re exploring similar automation challenges, the key takeaway is simple: start with real human workflows, then let AI enhance them — not replace them blindly.</p>




 
    
    <div class="editor-media-text editor-media-text_style_1 editor-media-text_index_35">
        
<div class="editor-media-text__split">
    
                
        <div class="editor-media-text__left">
            <div class="editor-media-text__image">
                                
                                    <img decoding="async" class="editor-media-text__img" src="https://sam-solutions.com/wp-content/uploads/photo-Andrey-Kopanev.webp"
                                                                              
                            srcset="https://sam-solutions.com/wp-content/uploads/photo-Andrey-Kopanev.webp 1x, https://sam-solutions.com/wp-content/uploads/photo-Andrey-Kopanev-2x.webp 2x"
                                                  alt="Consulting on LLM deployment project"
                         width="200" height="292">
                        
            </div>     
        </div>   
        
    <div class="editor-media-text__right">
                                      
            <div class="editor-media-text__title">
                <div class="h4">Need to tackle a similar challenge?</div>            </div>
                
                            
            <div class="editor-media-text__descr">
                <div class="wysiwyg-editor"><p>For organizations facing challenges in lead generation, AI-powered assistants offer a scalable way to automate outreach, assess ICP fit, and generate qualified leads, while keeping conversations natural, respectful, and efficient.</p>
<p><b>Andrey Kopanev, Senior .NET Developer, AI Enthusiast</b></p>
</div>
            </div>
                
                            
            <div class="editor-media-text__event">
                                    <div class="editor-media-text__button button button_style_1">
                        <a class="button__inner" href="https://sam-solutions.com/contacts/">                                                            <div class="button__name">Let’s talk about your project</div>
                                                        <div class="button__icon">
                                <svg class="button__icon-svg" xmlns="http://www.w3.org/2000/svg" width="32" height="12" viewBox="0 0 32 12" fill="none">
                                    <path d="M30.9834 5.50256C31.2581 5.77731 31.2581 6.22278 30.9834 6.49753L26.5062 10.9747C26.2315 11.2494 25.786 11.2494 25.5113 10.9747C25.2365 10.6999 25.2365 10.2545 25.5113 9.97977L29.491 6.00004L25.5113 2.02032C25.2365 1.74558 25.2365 1.30014 25.5113 1.02539C25.786 0.750651 26.2315 0.750651 26.5062 1.02539L30.9834 5.50256ZM0 5.29652H30.4859V6.70357H1.09781e-07L0 5.29652Z" fill="#FCFCFC"/>
                                </svg>
                            </div>
                        </a>                    </div>
                            </div>
            </div>
    
</div>    </div>
    



<div id="video-content" class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Developing an AI Assistant Prototype for Automated Lead Discovery and Qualification" width="500" height="281" class="lazyload"
                            data-src="https://www.youtube.com/embed/oP2Qzk5tikU?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope;  web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
</div></div>
]]></content:encoded>
					
					<wfw:commentRss>https://sam-solutions.com/blog/developing-ai-assistant-for-automated-lead-generation/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://www.youtube.com/watch?v=oP2Qzk5tikU" medium="video">
			<media:player url="https://www.youtube.com/watch?v=oP2Qzk5tikU" />
			<media:title type="plain">Developing an AI Assistant Prototype for Automated Lead Discovery and Qualification</media:title>
			<media:description type="html"><![CDATA[Finding potential clients for software development services is rarely a straightforward task. In practice, it often means manually monitoring chats, forums, and social networks, identifying promising conversations, and then reaching out to people one by one with similar introductory messages. This process is time-consuming, repetitive, and difficult to scale. Look at a practical case study on building an AI assistant for automated lead discovery and qualification in software development, combining LLMs, scripted conversation flows, and human-like messaging to scale outreach effectively and respectfully.]]></media:description>
			<media:thumbnail url="https://i.ytimg.com/vi/oP2Qzk5tikU/maxresdefault.jpg" />
			<media:rating scheme="urn:simple">adult</media:rating>
		</media:content>
	</item>
		<item>
		<title>How We Built a Production-Grade RAG Engine for a Website AI Chatbot</title>
		<link>https://sam-solutions.com/blog/how-we-built-a-production-grade-rag-engine/</link>
					<comments>https://sam-solutions.com/blog/how-we-built-a-production-grade-rag-engine/#respond</comments>
		
		<dc:creator><![CDATA[Maryia Shapel]]></dc:creator>
		<pubDate>Fri, 16 Jan 2026 13:14:36 +0000</pubDate>
				<guid isPermaLink="false">https://sam-solutions.com/?post_type=article&#038;p=32057</guid>

					<description><![CDATA[(If you prefer video content, please watch the concise video summary of this article below) Modern websites don’t suffer from a lack of information — on the contrary, they often suffer from information overload. Blogs, news, service pages, event announcements, case studies… everything is useful, everything is scaling, and everything is scattered across navigation paths [&#8230;]]]></description>
										<content:encoded><![CDATA[<span id="more-32057"></span>
<!--noteaser-->



<iframe style="margin: 0;" width="100%" height="115" scrolling="no" frameborder="no" allow="autoplay" title="How We Built a Production-Grade RAG Engine for a Website AI Chatbot" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/soundcloud%253Atracks%253A2248690610&#038;color=%23ff5500&#038;auto_play=false&#038;hide_related=false&#038;show_comments=false&#038;show_user=false&#038;show_reposts=false&#038;show_teaser=false" rel="nofollow"></iframe><p style="font-size:14px;"><em>(If you prefer video content, please <a href="#video-content">watch the concise video summary</a> of this article below)</em></p>



<p class="wp-block-paragraph">Modern websites don’t suffer from a lack of information — on the contrary, they often suffer from information overload. Blogs, news, service pages, event announcements, case studies… everything is useful, everything is scaling, and everything is scattered across navigation paths that make little sense to a visitor who just wants one specific answer.</p>




 
    
    <div class="editor-content editor-content_style_1 editor-content_index_36">
        
    
    <div class="editor-content__descr">
        <div class="wysiwyg-editor"><h2>Key Takeaways</h2>
<ul>
<li>Production-grade AI chatbots require a RAG architecture that retrieves the right content first instead of relying on oversized prompts or fine-tuned models.</li>
<li>Clean content extraction, semantic chunking, and hybrid retrieval (dense + lexical reranking) are critical to answer quality and relevance.</li>
<li>Local, modular RAG components provide stronger control over cost, privacy, and scalability than fully managed cloud ingestion pipelines.</li>
<li>High-quality chatbot answers are an architectural outcome — driven by ingestion, retrieval, and ranking decisions, not by the LLM alone.</li>
</ul>
</div>
    </div>
    </div>
    



<p class="wp-block-paragraph">That’s exactly why we launched an internal RAG (Retrieval-Augmented Generation) project: to power a website chatbot that can answer questions based on the real site content. The chatbot can do it reliably and privately, without pretending that it “knows” things it has never seen.</p>



<p class="wp-block-paragraph">Explore the practical story of how we built it, what didn’t work at first, and what finally made the answers noticeably sharper.</p>




 
    
    <div class="editor-list-cta editor-list-cta_style_1 editor-list-cta_index_37">
        
    <div class="editor-list-cta__items">
                                    
                    			    				<style>
    					.editor-list-cta_index_37 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-button {
            				            				
            				            				        							    background: linear-gradient(to right, #a067e8, #527eff);
    							            				    					}
    				</style>
    			    			
    			    			    				<style>
    					.editor-list-cta_index_37 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left {
    					    padding-left: 10px;
    					}
    					
    					.editor-list-cta_index_37 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					    left: 0;
    					    top: 50%;
    					    width: 3px;
    					    content: '';
    					    position: absolute;
    					    margin-left: -10px;
    					    height: calc(100% + 10px);
    					    transform: translateY(-50%);

            				            					    							    background: linear-gradient(45deg, #527eff, #a067e8);
    							            				    					}
    					
    					@media (max-width: 475px) {
    					    .editor-list-cta_index_37 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					        width: 2px;
                                margin-left: -3.5px;
    					    }
    					}
    				</style>
    			                
                    			    			        
    			
                    			
                                
                <div class="editor-list-cta__item editor-list-cta__item_index_1">
                    <div class="editor-list-cta__item-inner">
            		                		                                <div class="editor-list-cta__item-left">
                                                                                                    <div class="editor-list-cta__item-title"><p><strong>Leverage AI to transform your business</strong> with custom solutions from SaM Solutions’ expert developers.</p>
</div>
                                                	        </div>
            	        
            	                    	            <div class="editor-list-cta__item-right">
            	                                    	                                	                                	            <div class="editor-list-cta__item-event">
                    	                <div class="editor-list-cta__item-button button button_style_1">
                                            <a class="button__inner" href="/services/ai-software-development/?utm_source=blog&#038;utm_medium=post_ID_32057&#038;utm_campaign=cta_post_content_16263">                                                <div class="button__name">View offer</div>
                                            </a>                                        </div>
                                    </div>    
                                                                        
            	            </div>
            	                	        </div>
                </div>
                        </div>
    </div>
    



<h2 class="wp-block-heading">Client’s Business Request</h2>



<p class="wp-block-paragraph">The project started with a client request to design and implement a scalable AI platform. </p>



<p class="wp-block-paragraph"><strong>Our ultimate objective was to create a system that would:</strong></p>



<ul class="wp-block-list">
<li>register and access the system;</li>



<li>connect several different models;</li>



<li>embed an AI pop-up chat on any website.</li>
</ul>



<p class="wp-block-paragraph"><strong>Within the MCP scope, the chatbot required a few definite features:</strong></p>



<ul class="wp-block-list">
<li>answer user questions with the help of website content (blogs, articles, news, event pages);</li>



<li>send an email from the chat flow (so a visitor can ask questions, provide missing details, and trigger outreach without the necessity to file a classic “Contact us” form).</li>
</ul>



<h2 class="wp-block-heading">Understanding the Real Problem</h2>



<p class="wp-block-paragraph">A website chatbot may look simple at first glance. Nevertheless, it seems so until you try to make it accurate. The core constraint was straightforward:</p>



<ul class="wp-block-list">
<li>A general LLM doesn’t know the сurrent state of the website by default.</li>



<li>Large cloud-based language models introduce significant concerns around cost, privacy, and dependency on external infrastructure. It is especially significant when it is applied beyond public web content to internal or sensitive knowledge.</li>



<li>As a result, many teams prefer to choose smaller, local models. However, this shift exposes a new bottleneck: limited context windows. When you feed entire documents or pages into a single prompt that no longer scales, the inputs get truncated, and irrelevant sections may consume valuable context. So, the “one huge prompt” approach becomes fragile and inefficient.</li>
</ul>



<p class="wp-block-paragraph">So instead of “teaching” the model everything at once, we chose the approach that works in real production systems: retrieve the right pieces of content first, then generate the answer from those pieces. That’s <a href="/blog/rag-llm-architecture/">RAG</a>.</p>



<h2 class="wp-block-heading">Exploring Possible Solutions</h2>



<p class="wp-block-paragraph">We looked at several possible directions before committing:</p>



<h3 class="wp-block-heading has-medium-font-size"><strong>Option 1: “Just prompt it with the page”</strong></h3>



<p class="wp-block-paragraph"><strong>Why it failed:</strong></p>



<ul class="wp-block-list">
<li>Content pages embed LLM-irrelevant noise (e.g., navigation, repeated sections, hidden elements, cookie notices) unrelated to query-relevant information;</li>



<li>Context limitations mean the model discards content anyway;</li>



<li>Answers may vary depending on what got cut off.</li>
</ul>



<h3 class="wp-block-heading has-medium-font-size"><strong>Option 2: Cloud ingestion + managed search (e.g., Azure)</strong></h3>



<p class="wp-block-paragraph"><strong>Why we didn’t pick it for this stage:</strong></p>



<ul class="wp-block-list">
<li>Cost grows fast once you index frequently and scale usage;</li>



<li>Privacy and control become more complex in the long run, especially for internal extensions.</li>
</ul>



<h3 class="wp-block-heading has-medium-font-size"><strong>Option 3: Fine-tuning</strong></h3>



<p class="wp-block-paragraph"><strong>Why it didn’t fit:</strong></p>



<ul class="wp-block-list">
<li>Constant website updates mean constant re-training or drift;</li>



<li>Fine-tuning doesn’t automatically explain where you got that answer from;</li>



<li>It is also computationally expensive and demands deep ML expertise to train, maintain, and debug models effectively.</li>
</ul>



<h3 class="wp-block-heading has-medium-font-size"><strong>Option 4: RAG with local components</strong></h3>



<p class="wp-block-paragraph"><strong>Why we chose it:</strong></p>



<ul class="wp-block-list">
<li>We keep total control over data and cost;</li>



<li>We can prove the concept on public content first and then confidently extend to private knowledge later;</li>



<li>We can continuously update the knowledge base with the help of content re-indexing.</li>
</ul>




 
    
    <div class="editor-list-cta editor-list-cta_style_1 editor-list-cta_index_38">
        
    <div class="editor-list-cta__items">
                                    
                    			    				<style>
    					.editor-list-cta_index_38 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-button {
            				            				
            				            				        							    background: linear-gradient(to right, #a067e8, #527eff);
    							            				    					}
    				</style>
    			    			
    			    			    				<style>
    					.editor-list-cta_index_38 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left {
    					    padding-left: 10px;
    					}
    					
    					.editor-list-cta_index_38 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					    left: 0;
    					    top: 50%;
    					    width: 3px;
    					    content: '';
    					    position: absolute;
    					    margin-left: -10px;
    					    height: calc(100% + 10px);
    					    transform: translateY(-50%);

            				            					    							    background: linear-gradient(45deg, #527eff, #a067e8);
    							            				    					}
    					
    					@media (max-width: 475px) {
    					    .editor-list-cta_index_38 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					        width: 2px;
                                margin-left: -3.5px;
    					    }
    					}
    				</style>
    			                
                    			    			        
    			
                    			
                                
                <div class="editor-list-cta__item editor-list-cta__item_index_1">
                    <div class="editor-list-cta__item-inner">
            		                		                                <div class="editor-list-cta__item-left">
                                                                                                    <div class="editor-list-cta__item-title"><p><strong>Talk to our AI specialists </strong>about building smart, scalable software for your business.</p>
</div>
                                                	        </div>
            	        
            	                    	            <div class="editor-list-cta__item-right">
            	                                    	                                	                                	            <div class="editor-list-cta__item-event">
                    	                <div class="editor-list-cta__item-button button button_style_1">
                                            <a class="button__inner" href="/services/ai-software-development/#feedback?utm_source=blog&#038;utm_medium=post_ID_32057&#038;utm_campaign=cta_post_content_16282">                                                <div class="button__name">Contact us</div>
                                            </a>                                        </div>
                                    </div>    
                                                                        
            	            </div>
            	                	        </div>
                </div>
                        </div>
    </div>
    



<h2 class="wp-block-heading">How We Ran the Process</h2>



<p class="wp-block-paragraph">To deliver an internal RAG engine that spans ingestion, retrieval, LLM orchestration, and website embedding, we assembled a cross-functional team:</p>



<ul class="wp-block-list">
<li><strong>Project Manager (PM)</strong> — aligned stakeholders, defined iterations and milestones, and kept scope under control as long as requirements evolved;<br></li>



<li><strong>Architect</strong> — owned the target architecture, integration approach, and key technical decisions, such as security, scalability, and data flow;<br></li>



<li><strong>.NET Engineer</strong> — implemented the RAG services, retrieval pipeline, vector database integration, and MCP tools (search, actions);<br></li>



<li><strong>Frontend Engineer</strong> — built the chatbot UI and the user flows needed to embed it on the website and make it usable in real sessions;<br></li>



<li><strong>2 Java Engineers</strong> — supported Java-based backend services and integrations; developed the MCP platform in both .NET and Java, with a unified architecture and the Java implementation selected for production; built and maintained CI/CD, Kubernetes, GitOps, and monitored the platform stability.</li>
</ul>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_39">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/Team-composition.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/Team-composition.webp 1x, https://sam-solutions.com/wp-content/uploads/Team-composition@2x.webp 2x"
                  
         alt="How We Ran the Process team composition"
         width="824" height="309">
    </div>
    



<p class="wp-block-paragraph">This setup let us iterate quickly and improve the final answer quality, while MCP’s formalized communication protocol enabled a multi-language, heterogeneous team to collaborate without depending on a single skill set. We built the solution in iterations — the first one worked “technically,” but not “product-wise.”</p>



<h3 class="wp-block-heading has-medium-font-size"><strong>Version 1: A quick Python prototype (worked, but messy)</strong></h3>



<p class="wp-block-paragraph"><strong>We started with a script that:</strong></p>



<ul class="wp-block-list">
<li>crawled pages;</li>



<li>extracted content;</li>



<li>generated JSON files with extracted content;</li>



<li>which were later processed by a separate utility to create embeddings and store them in the vector database.</li>
</ul>



<p class="wp-block-paragraph"><strong>What went wrong:</strong></p>



<ul class="wp-block-list">
<li>We were embedding entire pages, including HTML and tags, which produced lots of irrelevant semantic noise;</li>



<li>We also hit practical issues around stable text-to-embedding conversion and consistency.</li>
</ul>



<p class="wp-block-paragraph"><strong>Result:</strong> the chatbot could answer, but its responses often felt fuzzy, overly broad, or based on the wrong fragment.</p>



<h3 class="wp-block-heading has-medium-font-size"><strong>Version 2: A microservice-based pipeline (cleaner, scalable)</strong></h3>



<p class="wp-block-paragraph">Once our microservices team got involved, we redesigned the ingestion approach: instead of “walking” through cross-links, the service used site APIs where possible. The microservice:</p>



<ul class="wp-block-list">
<li>pulled content;</li>



<li>cleaned it;</li>



<li>split it into chunks;</li>



<li>embedded those chunks;</li>



<li>and pushed them into the vector database.</li>
</ul>



<p class="wp-block-paragraph">This alone improved relevance, because the model stopped “learning” navigation menus and repeated UI blocks.</p>



<h2 class="wp-block-heading">Designing the RAG Strategy</h2>



<p class="wp-block-paragraph">RAG success depends on two things:</p>



<ol class="wp-block-list">
<li><strong>How you chunk content</strong></li>



<li><strong>How do you rank what you retrieved</strong></li>
</ol>



<h3 class="wp-block-heading has-medium-font-size"><strong>Chunking experiments we tested</strong></h3>



<p class="wp-block-paragraph">We explored multiple strategies, since manual, human-driven splitting is slow, hard to maintain with updates, and not a scalable approach.</p>



<p class="wp-block-paragraph"><strong>What we explored included:</strong></p>



<ul class="wp-block-list">
<li>Sentence-window chunking</li>



<li>Semantic chunking (split/merge based on embedding similarity)</li>



<li>Hybrid approaches that combined fixed windows and semantic merging</li>
</ul>



<p class="wp-block-paragraph"><strong>Libraries and components we used in this exploration:</strong></p>



<ul class="wp-block-list">
<li>TextChunker (Microsoft.SemanticKernel.Text)</li>



<li>drittich.SemanticSlicer</li>



<li>SemanticChunker.NET</li>



<li>Custom strategies like SemanticDoubleParseMergeStrategy and WindowChunkStrategy</li>
</ul>



<p class="wp-block-paragraph">WindowChunkStrategy (the idea):</p>



<ul class="wp-block-list">
<li>Take a 3-sentence window</li>



<li>Shift by one sentence → next window</li>



<li>Compare embeddings of neighboring windows</li>



<li>Merge them if they’re semantically close</li>
</ul>



<p class="wp-block-paragraph">This helped keep meaning intact without creating giant blobs of text. In the end, we settled on SemanticSlicer.</p>



<h3 class="wp-block-heading has-medium-font-size"><strong>Final retrieval flow (the part that changed everything)</strong></h3>



<p class="wp-block-paragraph">Our first implementation did a basic vector search. It worked — but not as well as we needed. So we added two key steps: query enrichment and reranking. Here’s the simplified pipeline:</p>



<p class="wp-block-paragraph">Here’s the simplified pipeline after the user asks a question in chat (example: “What expertise does your company have?”):</p>




 
    
    <div class="editor-list-step editor-list-step_style_1 editor-list-step_index_40">
        
    <div class="editor-list-step__items">
                                    <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">Query enrichment</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>Before embedding the query, we send it to the LLM that expands it semantically. Example of transformation: “the company’s expertise”→ “the company’s expertise, successful projects, clients, competencies, industries.” We now run retrieval twice: vector search for the original query and vector search for the enriched query.</p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">Reranking (BM25 / lexical relevance)</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>The system retrieves 15 results from the enriched query and 10 from the original one, then reranks them and selects the top 5 for response generation. This favors chunks with strong keyword overlap and rare, distinctive terms. Built as a modular microservice platform, all retrieval and ranking parameters are configurable on the fly to optimize relevance, performance, and scale. We select the top 5 chunks. Those records become the grounded context for the LLM answer.</p>
</div>
                        </div>
                                    </div>  
                        
    </div>
    </div>
    



<p class="wp-block-paragraph">This is where responses noticeably improved: tighter answers, fewer irrelevant citations, and better alignment with how humans actually ask questions.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_41">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/Scheme.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/Scheme.webp 1x, https://sam-solutions.com/wp-content/uploads/Scheme@2x.webp 2x"
                  
         alt="Designing our RAG Strategy"
         width="824" height="1236">
    </div>
    



<h2 class="wp-block-heading">Key Features Implemented</h2>



<p class="wp-block-paragraph">The following features form the core of the system, enabling reliable actions, accurate retrieval, and traceable knowledge grounding in real production use:</p>




 
    
    <div class="editor-list-step editor-list-step_style_2 editor-list-step_index_42">
        
    <div class="editor-list-step__items">
                                    <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">MCP tools for real actions</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>Within MCP, we implemented tools that the assistant can call programmatically: vector database search (semantic retrieval) and email sending (lead capture / follow-up).</p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">A knowledge base built from real website content</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>Each chunk is traceable back to its source page. Re-indexing runs on a schedule (initially every few hours / up to twice per day depending on the pre-set configuration).</p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">Hybrid retrieval quality improvements</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>We added two-pass retrieval (original + enriched query), BM25 reranking, top-K selection to keep prompts small and relevant, which, in essence, describes how RAG works overall, since its core task is to find the documents most relevant to a given query.</p>
</div>
                        </div>
                                    </div>  
                        
    </div>
    </div>
    



<p class="wp-block-paragraph"><h2 class="wp-block-heading">Overcoming Challenges</h2></p>




 
    
    <div class="editor-list-icons editor-list-icons_style_1 editor-list-icons_index_43">
        
    <div class="editor-list-icons__items">
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><div class="h5">Content noise and “embedding the wrong things”</div>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>Early on, embedding raw page content caused the assistant to “learn” the wrong signals. Fix: cleaner extraction + chunking focused on meaningful text blocks.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/1-Content-noise.svg"
                                                                                                  alt="1-The content noise"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><div class="h5">Names and multilingual edge cases</div>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>Some user questions were not in English (e.g., “Who is the named person?”). Embedding models that aren’t strong in the foreign languages can misfire. For instance, the system can retrieve another person who has the same name, just because the vector similarity isn’t precise enough. Mitigation ideas we explored:</p>
<ul>
<li>hybrid sparse+dense retrieval</li>
<li>adding extra signals for person-name detectionq</li>
</ul>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/2-Name.svg"
                                                                                                  alt="2-Names"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><div class="h5">Evaluation and testing</div>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>RAG retrieval itself is deterministic for the same inputs, but its non-determinism comes from query embedding generation and from how the LLM processes and phrases the retrieved context. We learned that the most stable testing approach is to validate:</p>
<ul>
<li>retrieval correctness (did we fetch the right chunks?)</li>
<li>answer grounding (did the answer use the provided context?)uo</li>
</ul>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/3-Evaluation.svg"
                                                                                                  alt="3-The Evaluation"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><div class="h5">Freshness and indexing latency</div>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>New content doesn’t become searchable instantly — ingestion takes time. We had to balance:</p>
<ul>
<li>infrastructure load</li>
<li>the user expectation that “the chatbot should know what we posted today”</li>
<li>indexing frequency, which was fully configurable (as often as every 15 minutes)</li>
</ul>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/4-Freshness.svg"
                                                                                                  alt="4-The freshness"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
            </div>
    </div>
    



<h2 class="wp-block-heading">Results and Business Impact</h2>



<p class="wp-block-paragraph">Even at this stage, the impact is already clear:</p>



<h3 class="wp-block-heading has-medium-font-size"><strong>What changed for users</strong></h3>



<ul class="wp-block-list">
<li>Visitors can ask questions in natural language instead of hunting through menus;</li>



<li>The chatbot provides faster discovery across blogs, articles, and updates;</li>



<li>The email tool enables a smoother lead flow: ask a question → provide missing details → send a message without the traditional form of friction.</li>
</ul>



<h3 class="wp-block-heading has-medium-font-size"><strong>What changed for the business</strong></h3>



<ul class="wp-block-list">
<li>Better content utilization: valuable pages are not being “buried” anymore;</li>



<li>Scalable approach: we can extend the same architecture beyond public pages;</li>



<li>Cost and privacy control: the system is built around retrieving only what’s needed, rather than pushing everything into external prompts.</li>
</ul>



<h2 class="wp-block-heading">What’s Next</h2>



<p class="wp-block-paragraph">This project is still evolving. The next steps are focused on reliability and scale:</p>



<ul class="wp-block-list">
<li>Automated evaluation for retrieval + grounded answers</li>



<li>Improved multilingual handling (especially for names and short queries)</li>



<li>Stronger hybrid retrieval (sparse + dense) to reduce “false friends” in vectors</li>



<li>Finalizing infrastructure pieces (AI server and RAG collection setup)</li>



<li>Revisiting multi-site coverage (the US site may run its own MCP server)</li>
</ul>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">This project demonstrates how a production-grade RAG architecture can turn an AI chatbot from a surface-level interface into a reliable knowledge access layer for a growing website. By combining structured content ingestion, semantic chunking, hybrid retrieval, and controlled LLM orchestration, we built a system that delivers accurate, grounded answers based on real content — not assumptions or hallucinations.&nbsp;</p>



<p class="wp-block-paragraph">The solution improves content discoverability for users and establishes a foundation that can be extended to internal knowledge bases, multi-language environments, and data sources. Most importantly, it proves that high-quality AI assistants are not created by prompts alone, but by deliberate architectural decisions across data, retrieval, and infrastructure.</p>



<div id="video-content" class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="How We Built a Production-Grade RAG Engine for a Website AI Chatbot" width="500" height="281" class="lazyload"
                            data-src="https://www.youtube.com/embed/7b-QyqfFVmY?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope;  web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
</div></div>




 
    
    <div class="editor-media-text editor-media-text_style_1 editor-media-text_index_44">
        
<div class="editor-media-text__split">
    
                
        <div class="editor-media-text__left">
            <div class="editor-media-text__image">
                                
                                    <img decoding="async" class="editor-media-text__img" src="https://sam-solutions.com/wp-content/uploads/photo-Andrey-Kopanev.webp"
                                                                              
                            srcset="https://sam-solutions.com/wp-content/uploads/photo-Andrey-Kopanev.webp 1x, https://sam-solutions.com/wp-content/uploads/photo-Andrey-Kopanev-2x.webp 2x"
                                                  alt="Consulting on LLM deployment project"
                         width="200" height="292">
                        
            </div>     
        </div>   
        
    <div class="editor-media-text__right">
                                      
            <div class="editor-media-text__title">
                <div class="h4">Need to tackle a similar challenge?</div>            </div>
                
                            
            <div class="editor-media-text__descr">
                <div class="wysiwyg-editor"><p>For organizations facing similar challenges with corporate content translation and localization, locally deployed AI models offer a powerful alternative to traditional translation methods, balancing autonomy, control, and performance in one integrated solution.</p>
<p><b>Andrey Kopanev, Senior .NET Developer, AI Enthusiast</b></p>
</div>
            </div>
                
                            
            <div class="editor-media-text__event">
                                    <div class="editor-media-text__button button button_style_1">
                        <a class="button__inner" href="https://sam-solutions.com/contacts/">                                                            <div class="button__name">Let’s talk about your project</div>
                                                        <div class="button__icon">
                                <svg class="button__icon-svg" xmlns="http://www.w3.org/2000/svg" width="32" height="12" viewBox="0 0 32 12" fill="none">
                                    <path d="M30.9834 5.50256C31.2581 5.77731 31.2581 6.22278 30.9834 6.49753L26.5062 10.9747C26.2315 11.2494 25.786 11.2494 25.5113 10.9747C25.2365 10.6999 25.2365 10.2545 25.5113 9.97977L29.491 6.00004L25.5113 2.02032C25.2365 1.74558 25.2365 1.30014 25.5113 1.02539C25.786 0.750651 26.2315 0.750651 26.5062 1.02539L30.9834 5.50256ZM0 5.29652H30.4859V6.70357H1.09781e-07L0 5.29652Z" fill="#FCFCFC"/>
                                </svg>
                            </div>
                        </a>                    </div>
                            </div>
            </div>
    
</div>    </div>
    
]]></content:encoded>
					
					<wfw:commentRss>https://sam-solutions.com/blog/how-we-built-a-production-grade-rag-engine/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://www.youtube.com/watch?v=7b-QyqfFVmY" medium="video">
			<media:player url="https://www.youtube.com/watch?v=7b-QyqfFVmY" />
			<media:title type="plain">How We Built a Production-Grade RAG Engine for a Website AI Chatbot</media:title>
			<media:description type="html"><![CDATA[Modern websites don’t suffer from a lack of information — on the contrary, they often suffer from information overload. Blogs, news, service pages, event announcements, case studies… everything is useful, everything is scaling, and everything is scattered across navigation paths that make little sense to a visitor who just wants one specific answer.]]></media:description>
			<media:thumbnail url="https://i.ytimg.com/vi/K8eNmPyVmzc/maxresdefault.jpg" />
			<media:rating scheme="urn:simple">adult</media:rating>
		</media:content>
	</item>
		<item>
		<title>Internal AI Deployment for Seamless Content Translation: A Real-Life Project Story</title>
		<link>https://sam-solutions.com/blog/internal-ai-deployment-for-seamless-content-translation/</link>
					<comments>https://sam-solutions.com/blog/internal-ai-deployment-for-seamless-content-translation/#respond</comments>
		
		<dc:creator><![CDATA[Natallia Sakovich]]></dc:creator>
		<pubDate>Mon, 28 Jul 2025 08:53:35 +0000</pubDate>
				<guid isPermaLink="false">https://sam-solutions.com/?post_type=article&#038;p=21697</guid>

					<description><![CDATA[Internal AI Deployment for Seamless Content Translation: A Real-Life Project Story (If you prefer video content, please watch the concise video summary of this article below) In today’s multilingual work environments, fast and reliable localization is essential. At SaM Solutions, we recently tackled the challenge of translating a large volume of internal content into English [&#8230;]]]></description>
										<content:encoded><![CDATA[<span id="more-21697"></span>
<!--noteaser-->



<iframe
style="margin:0"
src="https://player.rss.com/sam-solutions/2145365?theme=default"
width="100%"
height="auto"
title="Internal AI Deployment for Seamless Content Translation: A Real-Life Project Story"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen scrolling="no">

<a target="_blank" rel="noopener" href="https://rss.com/podcasts/sam-solutions/2145365/">Internal AI Deployment for Seamless Content Translation: A Real-Life Project Story</a>

</iframe>

<p style="font-size:14px;margin-top:0;"><em>(If you prefer video content, please <a href="#video-content">watch the concise video summary</a> of this article below)</em></p>




 
    
    <div class="editor-content editor-content_style_1 editor-content_index_45">
        
    
    <div class="editor-content__descr">
        <div class="wysiwyg-editor"><h2>Key Takeaways</h2>
<ul>
<li>SaM Solutions implemented an AI-powered translation system to automatically translate more than a thousand internal CMS pages, addressing the operational challenges of manual translation in a growing multilingual environment.</li>
<li>A self-hosted large language model (LLM) was chosen to maximize data security, avoid subscription costs, run within the corporate intranet, and allow deep customization.</li>
<li>The team built a modular architecture integrated into the Umbraco CMS workflow using tools like Hangfire for background job scheduling, balancing automated translation with manual editorial checks where needed.</li>
<li>The system translated large content volumes in hours instead of an estimated weeks of manual work, showing efficiency gains and scalability.</li>
</ul>
</div>
    </div>
    </div>
    



<p class="wp-block-paragraph"><em>In today’s multilingual work environments, fast and reliable localization is essential. </em>At SaM Solutions, we recently tackled the challenge of translating a large volume of internal content into English by deploying an <a href="/services/ai-software-development/">AI-driven solution</a>. This case study details how our team leveraged a locally hosted large language model (LLM) to automate translation directly within our content management system (CMS), achieving efficiency, cost savings, and full data control.</p>




 
    
    <div class="editor-list-cta editor-list-cta_style_1 editor-list-cta_index_46">
        
    <div class="editor-list-cta__items">
                                    
                    			    				<style>
    					.editor-list-cta_index_46 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-button {
            				            				
            				            				        							    background: linear-gradient(to right, #a067e8, #527eff);
    							            				    					}
    				</style>
    			    			
    			    			    				<style>
    					.editor-list-cta_index_46 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left {
    					    padding-left: 10px;
    					}
    					
    					.editor-list-cta_index_46 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					    left: 0;
    					    top: 50%;
    					    width: 3px;
    					    content: '';
    					    position: absolute;
    					    margin-left: -10px;
    					    height: calc(100% + 10px);
    					    transform: translateY(-50%);

            				            					    							    background: linear-gradient(45deg, #527eff, #a067e8);
    							            				    					}
    					
    					@media (max-width: 475px) {
    					    .editor-list-cta_index_46 .editor-list-cta__item.editor-list-cta__item_index_1 .editor-list-cta__item-left:before {
    					        width: 2px;
                                margin-left: -3.5px;
    					    }
    					}
    				</style>
    			                
                    			    			        
    			
                    			
                                
                <div class="editor-list-cta__item editor-list-cta__item_index_1">
                    <div class="editor-list-cta__item-inner">
            		                		                                <div class="editor-list-cta__item-left">
                                                                                                    <div class="editor-list-cta__item-title"><p><strong>Leverage AI to transform your business</strong> with custom solutions from SaM Solutions’ expert developers.</p>
</div>
                                                	        </div>
            	        
            	                    	            <div class="editor-list-cta__item-right">
            	                                    	                                	                                	            <div class="editor-list-cta__item-event">
                    	                <div class="editor-list-cta__item-button button button_style_1">
                                            <a class="button__inner" href="/services/ai-software-development/?utm_source=blog&#038;utm_medium=post_ID_21697&#038;utm_campaign=cta_post_content_16263">                                                <div class="button__name">View offer</div>
                                            </a>                                        </div>
                                    </div>    
                                                                        
            	            </div>
            	                	        </div>
                </div>
                        </div>
    </div>
    



<h2 class="wp-block-heading"><strong>The Business Need</strong></h2>



<p class="wp-block-paragraph">As SaM Solutions continues to grow internationally, the need for multilingual communication across departments has become more pressing. Our internal portal, powered by <a href="/services/portals-wcms/umbraco/">Umbraco CMS</a>, serves as the central hub for news, articles, and corporate updates. With over a thousand pages of content in different languages (English, German, Polish, Lithuanian, etc.), we faced the operational challenge of ensuring this material would be accessible to all employees, regardless of the source or target language. This required a scalable solution for cross-translation between all corporate languages.</p>



<p class="wp-block-paragraph">Manual translation was evaluated but quickly ruled out due to the volume of material and time constraints. We needed an automated solution that could be integrated directly into our corporate portal, preserve data privacy, and ensure quality outputs with minimal manual intervention.</p>



<h2 class="wp-block-heading"><strong>Why We Chose a Locally Deployed LLM</strong></h2>



<p class="wp-block-paragraph">Cloud-based translation services were not considered due to concerns over data confidentiality and ongoing subscription costs. Instead, we opted for a self-hosted <a href="/blog/llm-architecture/">LLM</a> deployment. Here are some key benefits of this approach.</p>




 
    
    <div class="editor-list-icons editor-list-icons_style_1 editor-list-icons_index_47">
        
    <div class="editor-list-icons__items">
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Data security</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>All processing occurs on internal infrastructure with no third-party exposure, preventing critical data leakage.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/Data-security.svg"
                                                                                                  alt="Data security"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Lower long-term costs</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>After the initial on-premises setup, there are no recurring licensing fees and <a href="https://sam-solutions.com/services/software-engineering/api-development-services/">API</a> subscription payments. You can use the model on a long-term basis after a one-time deployment.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/Cost-4.svg"
                                                                                                  alt="Lower long-term costs"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Flexible configuration</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>You can configure every aspect of the system to match your specific needs. It supports locally deployed AI models as well as integrations with external providers like OpenAI, allowing you to choose, combine, or switch between models based on your tasks and infrastructure.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/Flexible-configuration.svg"
                                                                                                  alt="Flexible configuration"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Intranet-based solution</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>The model runs entirely within the corporate intranet, providing fast, reliable access for internal teams without requiring an internet connection. This setup aligns with strict network policies and supports secure, uninterrupted operation across internal systems.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/Intranet-based-solution.svg"
                                                                                                  alt="Intranet-based solution"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Full integration</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>A locally deployed AI model can be connected directly to the company’s internal applications through the development of <a href="https://sam-solutions.com/blog/model-context-protocol/">Model Context Protocols (MCP)</a>, ensuring the integration of different workflows.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/Integration-1.svg"
                                                                                                  alt="Full integration"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Custom tuning</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>The system is fully configurable to adapt to evolving content and language needs.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/Custom-tuning.svg"
                                                                                                  alt="Custom tuning"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Fine-tuning</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>With local deployment, we gain full control over the training process, enabling precise fine-tuning of the language model on our domain-specific data. This allows the AI to better understand internal terminology, writing style, and context-specific phrasing.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/Fine-tuning.svg"
                                                                                                  alt="Fine-tuning"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
            </div>
    </div>
    



<h2 class="wp-block-heading"><strong>Selecting the Right Model</strong></h2>



<p class="wp-block-paragraph">We evaluated several open-source LLMs with different quantization levels and various versions, including <strong>Qwen, Deepseek, Mistral Nemo, Phi4, and Gemma</strong>. Each was tested using the same prompt on a representative sample of articles. Our evaluation team assessed output quality across several dimensions: linguistic accuracy, tone consistency, handling of markup and abbreviations, and overall usability.</p>



<p class="wp-block-paragraph"><strong>Gemma 3</strong> emerged as the clear winner. It provided the most consistent and coherent translations and required the least post-processing. Based on these findings, we moved forward with Gemma as the foundation of our localization pipeline.</p>



<h2 class="wp-block-heading"><strong>Technical Implementation</strong></h2>



<h3 class="wp-block-heading">Architecture</h3>



<p class="wp-block-paragraph">We designed a modular and resilient architecture to integrate AI-powered translation seamlessly into our existing CMS infrastructure. The system identifies untranslated documents by scanning Umbraco metadata for content types such as News and Articles. Each qualifying document is assigned a discrete translation job, ensuring traceability and isolation of processing.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_48">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/internal-LLM-model-deployment.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/internal-LLM-model-deployment.webp 1x, https://sam-solutions.com/wp-content/uploads/internal-LLM-model-deployment-2x.webp 2x"
                  
         alt="How LLM model functions"
         width="820" height="1256">
    </div>
    



<p class="wp-block-paragraph">A conditional publishing flow was established:</p>



<ul class="wp-block-list">
<li><strong>News items </strong>(in case they are published in the original language) are published automatically upon successful translation.</li>



<li><strong>Articles</strong> are held in an unpublished state and submitted for manual review by designated editors. The reason is that articles are typically longer and often include specialized terminology (industry-specific or unique to our company), requiring a higher level of translation accuracy. This is especially important for corporate policies, ISO documentation, and other sensitive materials.&nbsp;</li>
</ul>



<p class="wp-block-paragraph">This approach balances automation with quality assurance, allowing rapid content delivery while maintaining editorial oversight where necessary.</p>



<h3 class="wp-block-heading">Key tools</h3>



<p class="wp-block-paragraph">We selected <strong>Hangfire</strong>, a robust job scheduling library for <a href="/services/technologies/dot_net/">.NET</a>, to manage translation workflows. Hangfire provides:</p>



<ul class="wp-block-list">
<li>Reliable background job execution</li>



<li>Retry logic for failed tasks</li>



<li>A built-in UI dashboard for monitoring and managing job status</li>
</ul>



<p class="wp-block-paragraph">To ensure secure and convenient access, we embedded the Hangfire dashboard directly into the Umbraco CMS interface and configured it with internal authentication controls.</p>



<p class="wp-block-paragraph">To tailor Hangfire to our specific needs, we introduced several key customizations:</p>



<ul class="wp-block-list">
<li><strong>Extended logging capabilities: </strong>We integrated a third-party logging library with Hangfire to enable detailed monitoring and easier debugging of background tasks.</li>



<li><strong>Task management extension:</strong> We developed additional functionality that allows us to manually add or restart specific tasks (such as translation jobs) directly within Hangfire. These controls were seamlessly embedded into the Hangfire dashboard, giving us better control and visibility over our job queue.</li>
</ul>



<p class="wp-block-paragraph">Translation jobs can be scheduled to run during off-peak hours to minimize resource contention and avoid disruptions to other internal processes. This allows us to maintain system performance while processing large volumes of content efficiently.</p>



<h2 class="wp-block-heading"><strong>Overcoming Challenges</strong></h2>



<p class="wp-block-paragraph">Throughout development and <a href="/services/qa-services/">testing</a>, several practical challenges emerged. We addressed each of them with targeted engineering decisions.</p>




 
    
    <div class="editor-list-icons editor-list-icons_style_1 editor-list-icons_index_49">
        
    <div class="editor-list-icons__items">
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Handling long documents</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>Large texts occasionally exceeded the model&#8217;s optimal input length. To ensure stability, we implemented a segmentation mechanism that breaks content into chunks not to exceed the selected model context window.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/Handling-long-documents.svg"
                                                                                                  alt="Handling long documents"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Managing complex formatting</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>Articles containing intricate markup or embedded HTML tags sometimes led to hallucinated or malformed output. Although such cases were rare, they prompted us to implement a post-translation validation step. This step checks formatting integrity and ensures consistency. If validation fails, the system automatically generates a log entry, flagging the content for manual review.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/Managing-complex-formatting.svg"
                                                                                                  alt="Managing complex formatting"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Abbreviation detection</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>Certain content comprised short strings of characters, such as acronyms or product codes, that do not require translation. A pre-processing filter was added to bypass these cases.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/Abbreviation-detection.svg"
                                                                                                  alt="Abbreviation detection"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Prompt tuning</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>While prompt quality is critical to translation fidelity, we found that even well-optimized prompts could yield unpredictable results. We continue to refine prompts based on observed edge cases.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/Prompt-tuning.svg"
                                                                                                  alt="Prompt tuning"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Retry logic</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>If a translation attempt fails or returns incomplete content, the job is automatically retried up to three times. Failures are logged for diagnostics.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/Retry.svg"
                                                                                                  alt="Retry logic"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Post-processing checks</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>Completed translations are scanned for indicators of failure, such as mixed-language output or untranslated segments. These are flagged for manual review to ensure quality control.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/Post-processing-checks.svg"
                                                                                                  alt="Post-processing checks"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
            </div>
    </div>
    




 
    
    <div class="editor-content editor-content_style_1 editor-content_index_50">
        
    
    <div class="editor-content__descr">
        <div class="wysiwyg-editor"><h2>Performance Metrics</h2>
<p>During the initial rollout, the system demonstrated solid performance and processing consistency:</p>
<ul>
<li><b>Initial batch processed</b>: more than 1,300 documents successfully translated</li>
<li><b>Average translation time per document</b><b> (under 1K words)</b>: approximately 30 seconds</li>
<li><b>Large documents</b><b> (over 1K words)</b>: typically completed in around 2 minutes</li>
<li><b>Rare outliers</b>: peak durations between 5 to 6 minutes</li>
<li><b>Operational efficiency:</b> Translation time for the full corpus was reduced from an estimated 2.5 weeks of manual work to a few hours.</li>
</ul>
<p>This level of performance met our expectations and confirmed the feasibility of ongoing automated localization for internal content workflows.</p>
</div>
    </div>
    </div>
    



<h2 class="wp-block-heading"><strong>What’s Next?</strong></h2>



<p class="wp-block-paragraph">To further streamline content localization, we are planning to develop a dedicated plugin for the Umbraco CMS. This plugin will introduce a “Translate with AI” button directly into the editorial interface, allowing users to initiate translation tasks with a single click.</p>



<p class="wp-block-paragraph">The solution will support both locally hosted and external LLMs, giving editors the flexibility to select the most suitable engine for their needs. Once completed, we plan to release the plugin to the broader community via the official Umbraco marketplace.</p>



<h2 class="wp-block-heading"><strong>Summing Up</strong></h2>



<p class="wp-block-paragraph">This project demonstrates how AI can be deployed responsibly and effectively to solve practical business problems. By combining careful model selection, strong system architecture, and thoughtful integration into existing workflows, we delivered a secure and scalable solution that improves our internal operations while preparing us for future localization demands.</p>




 
    
    <div class="editor-media-text editor-media-text_style_1 editor-media-text_index_51">
        
<div class="editor-media-text__split">
    
                
        <div class="editor-media-text__left">
            <div class="editor-media-text__image">
                                
                                    <img decoding="async" class="editor-media-text__img" src="https://sam-solutions.com/wp-content/uploads/photo-Andrey-Kopanev.webp"
                                                                              
                            srcset="https://sam-solutions.com/wp-content/uploads/photo-Andrey-Kopanev.webp 1x, https://sam-solutions.com/wp-content/uploads/photo-Andrey-Kopanev-2x.webp 2x"
                                                  alt="Consulting on LLM deployment project"
                         width="200" height="292">
                        
            </div>     
        </div>   
        
    <div class="editor-media-text__right">
                                      
            <div class="editor-media-text__title">
                <div class="h4">Need to tackle a similar challenge?</div>            </div>
                
                            
            <div class="editor-media-text__descr">
                <div class="wysiwyg-editor"><p>For organizations facing similar challenges with corporate content translation and localization, locally deployed AI models offer a powerful alternative to traditional translation methods, balancing autonomy, control, and performance in one integrated solution.</p>
<p><b>Andrey Kopanev, Senior .NET Developer, AI Enthusiast</b></p>
</div>
            </div>
                
                            
            <div class="editor-media-text__event">
                                    <div class="editor-media-text__button button button_style_1">
                        <a class="button__inner" href="https://sam-solutions.com/contacts/">                                                            <div class="button__name">Let’s talk about your project</div>
                                                        <div class="button__icon">
                                <svg class="button__icon-svg" xmlns="http://www.w3.org/2000/svg" width="32" height="12" viewBox="0 0 32 12" fill="none">
                                    <path d="M30.9834 5.50256C31.2581 5.77731 31.2581 6.22278 30.9834 6.49753L26.5062 10.9747C26.2315 11.2494 25.786 11.2494 25.5113 10.9747C25.2365 10.6999 25.2365 10.2545 25.5113 9.97977L29.491 6.00004L25.5113 2.02032C25.2365 1.74558 25.2365 1.30014 25.5113 1.02539C25.786 0.750651 26.2315 0.750651 26.5062 1.02539L30.9834 5.50256ZM0 5.29652H30.4859V6.70357H1.09781e-07L0 5.29652Z" fill="#FCFCFC"/>
                                </svg>
                            </div>
                        </a>                    </div>
                            </div>
            </div>
    
</div>    </div>
    



<div id="video-content" class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Internal AI Deployment for Seamless Content Translation: A Real-Life Project Story #ai #business" width="500" height="281" class="lazyload"
                            data-src="https://www.youtube.com/embed/KG809O5_AXI?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope;  web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
</div></div>
]]></content:encoded>
					
					<wfw:commentRss>https://sam-solutions.com/blog/internal-ai-deployment-for-seamless-content-translation/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://www.youtube.com/watch?v=KG809O5_AXI" medium="video">
			<media:player url="https://www.youtube.com/watch?v=KG809O5_AXI" />
			<media:title type="plain">Internal AI Deployment for Seamless Content Translation: A Real-Life Project Story</media:title>
			<media:description type="html"><![CDATA[Are you facing the challenge of translating over a thousand pages for a multilingual workforce? We've got the solution!]]></media:description>
			<media:thumbnail url="https://i.ytimg.com/vi/KG809O5_AXI/maxresdefault.jpg" />
			<media:rating scheme="urn:simple">adult</media:rating>
		</media:content>
	</item>
		<item>
		<title>How We Built a Risk Management System for an International Company: A Real-Life Project Story</title>
		<link>https://sam-solutions.com/blog/how-we-built-a-risk-management-system/</link>
					<comments>https://sam-solutions.com/blog/how-we-built-a-risk-management-system/#respond</comments>
		
		<dc:creator><![CDATA[Natallia Sakovich]]></dc:creator>
		<pubDate>Fri, 16 May 2025 15:24:41 +0000</pubDate>
				<guid isPermaLink="false">https://sam-solutions.com/?post_type=article&#038;p=17846</guid>

					<description><![CDATA[How We Built a Risk Management System for an International Company: A Real-Life Project Story (If you prefer video content, please watch the concise video summary of this article below) Managing risks across multiple international branches while staying compliant with internal security policies is no easy task. That was exactly the challenge our client, a [&#8230;]]]></description>
										<content:encoded><![CDATA[<span id="more-17846"></span>
<!--noteaser-->



<iframe style="margin:0" src="https://player.rss.com/sam-solutions/2197105?theme=default" width="100%" height="auto" title="How We Built a Risk Management System for an International Company: A Real-Life Project Story" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen scrolling="no"><a target="_blank" rel="noopener" href="https://rss.com/podcasts/sam-solutions/2197105">How We Built a Risk Management System for an International Company: A Real-Life Project Story</a> </iframe><p style="font-size:14px;margin-top:0;"><em>(If you prefer video content, please <a href="#video-content">watch the concise video summary</a> of this article below)</em></p>
</iframe>



<p class="wp-block-paragraph"><em>Managing risks across multiple international branches while staying compliant with internal security policies is no easy task. </em>That was exactly the challenge our client, a global enterprise with operations spanning several continents, was facing. They needed a centralized Risk Management System that could tie together their security framework, user access rules, and workflow automation across the board.</p>



<p class="wp-block-paragraph"><a href="/services/dedicated-development-team/">SaM Solutions’ development team</a> built a custom enterprise risk management (ERM) module based on .NET, which became part of a larger system, to bring visibility, control, and efficiency to how the company handles risks.</p>



<p class="wp-block-paragraph">Read on to discover how we approached this project step by step. And if you’re tackling something similar, we’d be happy to talk.</p>




 
    
    <div class="editor-list-cta editor-list-cta_style_1 editor-list-cta_index_52">
        
    <div class="editor-list-cta__items">
                                    
                    			    			
    			    			                
                    			    			        
    			
                    			
                                
                <div class="editor-list-cta__item editor-list-cta__item_index_1">
                    <div class="editor-list-cta__item-inner">
            		                		                                <div class="editor-list-cta__item-left">
                                                                                                    <a href="/services/technologies/dot_net/?utm_source=blog&#038;utm_medium=post_ID_17846&#038;utm_campaign=cta_post_content_3868" class="editor-list-cta__item-title"><p><strong>Rely on SaM Solutions&#8217; vast expertise in .NET</strong> development to deliver your .NET-powered product or custom software.</p>
</a>                                                	        </div>
            	        
            	                    	            <div class="editor-list-cta__item-right">
            	                                                                            	                                	                                                    <div class="editor-list-cta__item-image">
                        					<img decoding="async" class="editor-list-cta__item-img" src="https://sam-solutions.com/wp-content/uploads/net.svg"
                        						 width="193" height="193"
                        						 alt="Rely on SaM Solutions&#8217; vast expertise in .NET development to deliver your .NET-powered product or custom software.
">
                                         </div>
                                         
                    	            
            	            </div>
            	                	        </div>
                </div>
                        </div>
    </div>
    



<h2 class="wp-block-heading"><strong>Client’s Business Request</strong></h2>



<p class="wp-block-paragraph">Our client is a global technology leader in the fields of electrification and automation, operating in over 140 countries with a workforce of around 160,000 employees. With such a massive international presence, they needed smarter tools to manage complexity, especially when it came to <a href="/blog/risk-managment-software/">risk management</a> and internal workflows.</p>



<p class="wp-block-paragraph">One of the key challenges was the legacy setup: many processes were built around Lotus Notes forms that had become outdated and difficult to maintain. The client saw this as an opportunity not just to migrate away from the old system, but to rethink and modernize their entire approach.</p>



<p class="wp-block-paragraph">At the top of the priority list was building a centralized <em>Risk Management Module</em><strong><em> </em></strong>— a solution that would work across all branches worldwide and integrate tightly with the company’s security systems, user roles and permissions, and approval workflows. The system also needed to provide <em>clear visualizations and intuitive organization of risks</em>, making it easier for teams to assess and act on risk data.</p>



<p class="wp-block-paragraph">But that was just part of the picture. Our task also included:</p>



<ul class="wp-block-list">
<li>Developing an <em>IT solution for the company’s R&amp;D center</em>, tailored to their internal processes and global collaboration needs.</li>



<li>Building a separate <em>module for reporting, manpower budgeting, cross-budgeting, and multistage approval workflows</em> to support <a href="/industries/financial-software-development/">financial</a> planning and transparency across departments.</li>
</ul>



<h2 class="wp-block-heading"><strong>Understanding the Client’s Needs</strong></h2>



<p class="wp-block-paragraph">When we started working with the client, it was clear they weren’t just looking for a new system — they were looking for a way to regain control over a set of critical processes that had become increasingly difficult to manage at scale.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><em>It wasn’t just about replacing a legacy system. They wanted to rethink how risk and process management worked across their global structure.</em></p>
</blockquote>



<p class="wp-block-paragraph">Like many large enterprises, they were dealing with the following hurdles.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_53">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/Risk-Management-Challenges.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/Risk-Management-Challenges.webp 1x, https://sam-solutions.com/wp-content/uploads/Risk-Management-Challenges@2x.webp 2x"
                  
         alt="challenges in risk management"
         width="856" height="251">
    </div>
    



<ul class="wp-block-list">
<li><strong>Fragmented data structure</strong>: Dozens of unorganized Lotus Notes forms were scattered across departments and countries, each with its own logic and purpose. There was no single source of truth for managing risks.</li>



<li><strong>Access issues</strong>: User permissions were inconsistent. Some users had too much access, while others didn’t have enough, leading to both security concerns and bottlenecks in daily operations.</li>



<li><strong>Data integrity problems</strong>: Without a unified system, keeping data accurate, up-to-date, and synchronized across branches was a constant challenge. This increased the risk of errors and compliance violations.</li>



<li><strong>Limited visibility</strong>: There was no possibility to visually manage risks and prioritize them. Managers couldn’t easily see where issues were occurring, how they were being handled, or what the current risk landscape looked like.</li>



<li><strong>Manual and inconsistent approvals</strong>: Especially in the R&amp;D division, approval workflows were overly complicated and handled manually, slowing down projects and introducing unnecessary friction.</li>



<li><strong>No unified risk management strategy</strong>: Each branch had its own way of assessing and responding to risks, which led to inconsistencies in how security and compliance standards were applied globally.</li>
</ul>



<p class="wp-block-paragraph">All of these issues were dragging the company down — slowing operations and creating unnecessary risks, both operational and regulatory. What they really needed was a single, flexible system that could bring everything together, work across all their international teams, and still be simple enough for anyone to use, both technical and non-technical staff.</p>



<h2 class="wp-block-heading"><strong>Exploring Possible Solutions</strong></h2>



<p class="wp-block-paragraph">At the start of the project, we explored a few different technology options. A complex, enterprise-wide, and security-focused system like this could technically be built using <a href="/services/technologies/java/">Java</a> frameworks, <a href="/services/technologies/python-development-services/">Python</a>-based back ends, <a href="/services/technologies/php/">PHP</a> solutions, or even low-code enterprise platforms. Each option had its merits. But with so many moving parts, integration requirements, and a need for long-term scalability and support, we knew the choice of tech stack would have a big impact on the project’s success.</p>



<h2 class="wp-block-heading"><strong>Why .NET Was the Best Option for the Project</strong></h2>



<p class="wp-block-paragraph">In the end, .NET was the clear winner, and not just because it can handle large enterprise-level data efficiently and has built-in authentication and role-based access controls.&nbsp;</p>



<p class="wp-block-paragraph">The client already had several internal systems running on .NET and a well-established IT department with .NET expertise. They also used Microsoft technologies across the organization, which made integration and ongoing support much smoother. By choosing .NET, we were able to build on their existing ecosystem, ensure maintainability due to long-term support from Microsoft, and keep development efficient by working closely with their in-house team. For this project, continuing the series of .NET-based solutions just made sense.</p>



<h2 class="wp-block-heading"><strong>How We Ran the Process</strong>&nbsp;</h2>



<p class="wp-block-paragraph">With the technology stack in place and goals aligned, we followed an agile development process, working in sprints and keeping communication open with the client’s stakeholders. This allowed us to stay adaptable, deliver early value, and continuously refine the system based on real-world feedback.</p>




 
    
    <div class="editor-retina-image editor-retina-image_style_1 editor-retina-image_index_54">
        

        
    <img decoding="async" class="editor-retina-image__img" src="https://sam-solutions.com/wp-content/uploads/risk-management-software-development.webp"
                
                     srcset="https://sam-solutions.com/wp-content/uploads/risk-management-software-development.webp 1x, https://sam-solutions.com/wp-content/uploads/risk-management-software-development@2x.webp 2x"
                  
         alt="SaM Solutions developed risk management software"
         width="856" height="294">
    </div>
    



<p class="wp-block-paragraph">To deliver a system of this scale and complexity, we assembled a cross-functional team, including:</p>



<ul class="wp-block-list">
<li><strong>2 Solution Architects (from the client’s side)</strong> — responsible for the overall system design and integration strategy</li>



<li><strong>2 .NET Developers</strong> — focused on <a href="/services/back-end-development-services/">back-end development</a> and business logic</li>



<li><strong>3 Full-Stack Developers </strong>— implemented UI components and data visualizations</li>



<li><strong>1 QA Engineer</strong> — handled test automation and manual testing across environments</li>
</ul>



<p class="wp-block-paragraph">This team worked in close collaboration with the client’s internal IT department, which also had .NET specialists who were gradually onboarded for support and future development.</p>



<h3 class="wp-block-heading">Key features implemented</h3>




 
    
    <div class="editor-list-step editor-list-step_style_2 editor-list-step_index_55">
        
    <div class="editor-list-step__items">
                                    <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">Risk management module (CRUD)</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>We built a full-featured module to manage risk data. Users can create, edit, and track risks at various organizational levels, with filtering options by region, department, and category. Risk categories in our module include:</p>
<ul>
<li><b>External risks arising from outside the organization, often beyond direct control.</b></li>
</ul>
<p><i>Geopolitical instability (e.g., sanctions, war), regulatory changes (e.g., GDPR, tax laws), market shifts or competitor disruption, supply chain breakdowns, currency fluctuations.</i></p>
<ul>
<li><b>Operational risks</b> related to internal processes, systems, or daily operations.</li>
</ul>
<p><i>IT system outages or software bugs, <a href="https://sam-solutions.com/industries/manufacturing-software-development-services/">manufacturing</a> defects, cyberattacks and data breaches, process delays or human error, inadequate maintenance or equipment failure.</i></p>
<ul>
<li><b>People and culture risks</b> tied to workforce, leadership, and organizational culture.</li>
</ul>
<p><i>Talent shortage or high turnover, resistance to organizational change, misaligned global teams, employee burnout or disengagement, leadership conflicts.</i></p>
<ul>
<li><b>Finance and organization risks </b>impacting financial stability and structural alignment.</li>
</ul>
<p><i>Budget overruns or inaccurate forecasts, fraud or financial misconduct, compliance failures in reporting, inefficient organizational structure, revenue decline from market or internal issues.</i></p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">Risk visualization</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>We developed an interactive interface for visualizing risks<b>,</b> grouped by categories, geographies, severity, and status. Color-coded dashboards, heat maps, and trend indicators help users quickly identify emerging threats and high-priority areas.</p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">Reporting module</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>A separate module allows users to generate reports for internal use, audits, or compliance reviews. Reports can be exported in various formats and filtered by different organizational levels and risk parameters.</p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">Flexible access control system</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>We implemented a dynamic, role-based access model that aligns with the client’s organizational hierarchy. Access rights can be configured at object level (e.g., specific risks or reports), ensuring users only see what’s relevant to them.</p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">IT solution for the R&#038;D center</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>We also built a dedicated internal system tailored for the company’s R&amp;D center, streamlining project tracking, approvals, and internal communications. The module was designed with flexibility in mind to accommodate cross-functional teams and iterative research workflows.</p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">Manpower budgeting and cross-budgeting module</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>To support broader operational planning, we delivered a budgeting tool that helps departments manage manpower forecasting, cost planning, and resource allocation. The system supports cross-budgeting scenarios between departments and includes multistage approval chains, making it easier to align financial plans across the organization.</p>
</div>
                        </div>
                                    </div>  
                                                <div class="editor-list-step__item">
                                            <div class="editor-list-step__item-title"><div class="h5">Integration with workflow and approval processes</div>                        </div>
                                                                <div class="editor-list-step__item-descr">
                            <div class="wysiwyg-editor"><p>We connected the risk module with the company&#8217;s internal workflow engine, enabling automated routing of risk items through various approval stages. This helped standardize decision-making and reduce manual back-and-forth.</p>
</div>
                        </div>
                                    </div>  
                        
    </div>
    </div>
    



<p class="wp-block-paragraph">The entire system is built on .NET Core, with an SQL Server back end and a JavaScript <a href="/services/front-end-development-services/">front end</a> based on the MVC pattern. We used CI/CD pipelines for streamlined deployments and hosted the system on Azure to ensure global availability, performance, and security.</p>



<h2 class="wp-block-heading"><strong>Overcoming Challenges During the Development Process</strong></h2>



<p class="wp-block-paragraph">Like any large-scale enterprise project, this one came with its own set of technical and organizational challenges. Here&#8217;s how we tackled the most critical ones.</p>




 
    
    <div class="editor-list-icons editor-list-icons_style_1 editor-list-icons_index_56">
        
    <div class="editor-list-icons__items">
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Designing a flexible risk visualization component</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>One of the key requirements was to give users a visual understanding of risks across the organization. But with so many variables — regions, categories, severity levels, timelines — we needed to build a visualization component that was both flexible and user-friendly.</p>
<p>Our team designed a modular, interactive interface that allows risks to be grouped, filtered, and color-coded in real time. The final result gave users the ability to instantly grasp their risk landscape — without being overwhelmed by data.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/risk-visualization.svg"
                                                                                                  alt="Graphic that shows the positive dynamics and growth."
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Fine-tuning access control with the client</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>Implementing access rights wasn&#8217;t just about roles, it had to reflect the client’s organizational structure, operational models, and internal policies. We collaborated with the client to map their real-world hierarchy into a flexible permission system that could handle region-specific and department-specific access.</p>
<p>Through several iterations, we arrived at a model that was granular enough to meet security needs, but still easy to manage from an admin perspective.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/access-control.svg"
                                                                                                  alt="Fine-tuning access control with the client"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Migrating data from legacy systems</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>The client had years’ worth of risk-related data stored across various Lotus Notes forms and spreadsheets. Migrating this into the new system while maintaining accuracy and relationships between records was a major task.</p>
<p>We created a custom migration pipeline to clean, transform, and import the data into the new structure, testing thoroughly to ensure data integrity at every step. This allowed users to start working in the new system without losing historical context.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/data-migration.svg"
                                                                                                  alt="Infographic that shows two arrows depicting data migration."
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
                    <div class="editor-list-icons__item">
                <div class="editor-list-icons__item-inner">
                                            <div class="editor-list-icons__item-title"><h3 class="h5">Enabling real-time updates without extra overhead</h3>                        </div>
                                                                <div class="editor-list-icons__item-descr">
                            <div class="wysiwyg-editor"><p>In risk management, timing is everything — risks can appear or change daily. The system needed to support frequent updates without creating friction for users or overload for the <a href="https://sam-solutions.com/services/software-engineering/maintenance-and-modernization/">support team</a>.</p>
<p>To solve this, we focused on minimizing manual effort through smart defaults, inline editing, and change tracking. We also designed the system to match expected performance metrics and developed a disaster recovery plan.</p>
</div>
                        </div>
                                                                                                            <div class="editor-list-icons__item-image">
                            <img decoding="async" class="editor-list-icons__item-img" src="https://sam-solutions.com/wp-content/uploads/real-time-updates.svg"
                                                                                                  alt="Enabling real-time updates without extra overhead"
                                 width="40" height="40">
                        </div>         
                                    </div>
            </div>
            </div>
    </div>
    




 
    
    <div class="editor-content editor-content_style_1 editor-content_index_57">
        
    
    <div class="editor-content__descr">
        <div class="wysiwyg-editor"><h2>Results and Business Impact</h2>
<p>By the end of the project, the client had a centralized, flexible system that fully met their needs for managing risk across a global enterprise. What started as a fragmented collection of legacy forms evolved into a modern, integrated platform that supports everything from daily operations to high-level strategic decision-making.</p>
<p>The new solution brought several key benefits:</p>
<ul>
<li><b>A single source of truth</b> for risk data across all branches and departments</li>
<li><b>Improved transparency and accountability</b> thanks to clear workflows and visualizations</li>
<li><b>Faster response to emerging risks</b>, with real-time updates and streamlined approvals</li>
<li><b>Stronger security and compliance</b> through role-based access and audit trails</li>
<li><b>Reduced manual work</b> and more efficient collaboration between teams</li>
</ul>
<p>The R&amp;D and budgeting modules gave internal teams the tools they needed to plan smarter, work more efficiently, and stay aligned across functions and geographies.</p>
<p>For the client, this wasn’t just a new system — it was a strategic upgrade that set the foundation for future innovation and growth.</p>
</div>
    </div>
    </div>
    




 
    
    <div class="editor-media-text editor-media-text_style_1 editor-media-text_index_58">
        
<div class="editor-media-text__split">
    
                
        <div class="editor-media-text__left">
            <div class="editor-media-text__image">
                                
                                    <img decoding="async" class="editor-media-text__img" src="https://sam-solutions.com/wp-content/uploads/photo-dzmitry-verasau.webp"
                                                                              
                            srcset="https://sam-solutions.com/wp-content/uploads/photo-dzmitry-verasau.webp 1x, https://sam-solutions.com/wp-content/uploads/photo-dzmitry-verasau@2x.webp 2x"
                                                  alt="Dzmitry Verasau"
                         width="200" height="292">
                        
            </div>     
        </div>   
        
    <div class="editor-media-text__right">
                                      
            <div class="editor-media-text__title">
                <div class="h4">Need to tackle a similar challenge?</div>            </div>
                
                            
            <div class="editor-media-text__descr">
                <div class="wysiwyg-editor"><p>If your organization is facing similar challenges with risk management, process automation, or system modernization — our team can help. At SaM Solutions, we combine deep technical expertise with a practical, business-oriented approach to deliver solutions that scale.</p>
<p><strong>Dzmitry Verasau, Chief .NET Technologist</strong></p>
</div>
            </div>
                
                            
            <div class="editor-media-text__event">
                                    <div class="editor-media-text__button button button_style_1">
                        <a class="button__inner" href="https://sam-solutions.com/contacts/">                                                            <div class="button__name">Let’s talk about your project</div>
                                                        <div class="button__icon">
                                <svg class="button__icon-svg" xmlns="http://www.w3.org/2000/svg" width="32" height="12" viewBox="0 0 32 12" fill="none">
                                    <path d="M30.9834 5.50256C31.2581 5.77731 31.2581 6.22278 30.9834 6.49753L26.5062 10.9747C26.2315 11.2494 25.786 11.2494 25.5113 10.9747C25.2365 10.6999 25.2365 10.2545 25.5113 9.97977L29.491 6.00004L25.5113 2.02032C25.2365 1.74558 25.2365 1.30014 25.5113 1.02539C25.786 0.750651 26.2315 0.750651 26.5062 1.02539L30.9834 5.50256ZM0 5.29652H30.4859V6.70357H1.09781e-07L0 5.29652Z" fill="#FCFCFC"/>
                                </svg>
                            </div>
                        </a>                    </div>
                            </div>
            </div>
    
</div>    </div>
    



<div id="video-content" class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="How We Built a Risk Management System for an International Company: A Real-Life Project Story" width="500" height="281" class="lazyload"
                            data-src="https://www.youtube.com/embed/fC30AlBjdFI?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope;  web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
</div></div>
]]></content:encoded>
					
					<wfw:commentRss>https://sam-solutions.com/blog/how-we-built-a-risk-management-system/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://www.youtube.com/watch?v=fC30AlBjdFI" medium="video">
			<media:player url="https://www.youtube.com/watch?v=fC30AlBjdFI" />
			<media:title type="plain">How We Built a Risk Management System for an International Company: A Real-Life Project Story</media:title>
			<media:description type="html"><![CDATA[Discover how we developed a robust risk management system for an international company. Explore the challenges, solutions, and technologies behind this real-life project success story.]]></media:description>
			<media:thumbnail url="https://i.ytimg.com/vi/fC30AlBjdFI/maxresdefault.jpg" />
			<media:rating scheme="urn:simple">adult</media:rating>
		</media:content>
	</item>
	</channel>
</rss>
