Build a Local AI Chat with NLWeb, Azure AI Search, and Azure AI Foundry.

I’ve spent the last few weeks exploring NLWeb, Microsoft’s open-source framework for grounded conversational AI. NLWeb (short for Natural Language Web) is a framework designed to make web systems interact more naturally with human language. It focuses on bridging the gap between traditional code-based interfaces and natural language processing (like we see in models such as ChatGPT), allowing us to build applications that understand, interpret, and respond to user input in a way that feels intuitive and conversational to them.

This guide is part one of a series. Here, we’ll stand up NLWeb locally, connect it to Azure AI Search and Azure AI Foundry, and ingest a custom dog breed dataset. In part two, we’ll create a small-but-powerful NLWeb proxy using .NET 10. In part three, learn how to add new functionality and remote API calls using NLWeb’s custom tools.

What is NLWeb?

NLWeb (Natural Language Web) is an open-source framework from Microsoft that lets you build web apps that understand natural language directly – without relying on heavyweight orchestration tools. It connects models like Azure AI Foundry’s GPT-4 and embedding services to existing databases or APIs through a “grounded” architecture.

What We’ll Cover

In this post, we’ll create a local chat demo together that connects:

  • NLWeb running locally
  • Azure AI Search for embeddings and filters
  • Azure AI Foundry for grounding and reasoning
  • A custom dog breed dataset (from The Kennel Club’s A-Z list)

1. Set Up Azure AI Search & Azure AI Foundry

Let’s begin by setting up the required resources via the Azure Portal.

Log into the Azure Portal and browse the marketplace. Create a new Azure AI Search service:

Azure Portal Marketplace showing Azure AI Search highlighted as part of setting up a local AI chat with NLWeb, Azure AI Search, and Azure AI Foundry.

Choose a name and following the steps, using default settings. Copy the endpoint and admin key – we’ll use them as environment variables later.

Azure AI Foundry

Open the Azure AI Foundry service and click Create:

Azure Portal Marketplace showing Azure AI Foundry highlighted for setup in a local AI chat project using NLWeb, Azure AI Search, and Azure AI Foundry.

Follow the default steps and create it in the same resource group as before. If all goes to plan, you’ll see the following resources in your resource group:

Azure Portal resource list showing created services for a local AI chat setup, including NLWeb AI Foundry, AI Foundry project, and Azure AI Search service in UK South.

Navigate to the new Azure AI Foundry resource and click the Go to Azure AI Foundry portal to be taken to ai.azure.com. For this demo, we’ll use gpt-4.1-mini and gpt-4.1 models, which are the defaults in NLWeb and will mean we have to make fewer configuration updates.

First, browse the model catalogue and select gpt-4.1-mini:

Azure AI Foundry model catalog showing available models for deployment, with GPT-4.1-mini highlighted for use in a local AI chat project using NLWeb, Azure AI Search, and Azure AI Foundry.

Click gpt-4.1-mini to view details and select Use this model, and Deploy:

Azure AI Foundry deployment window showing configuration for deploying the gpt-4.1-mini model in UK South for a local AI chat project using NLWeb, Azure AI Search, and Azure AI Foundry.

Follow the same steps and deploy a gpt-4.1 model.

Finally, copy the endpoint and API key, which will be the same for both.

Confirm Azure AI Foundry Deployments

To check that you have the required deployments, click on the Models + endpoints link on the bottom left of the Azure AI Foundry Overview page, where you can confirm the three models are present:

Azure AI Foundry interface showing successfully deployed models including gpt-4.1, gpt-4.1-mini, and text-embedding-3-small for a local AI chat project using NLWeb, Azure AI Search, and Azure AI Foundry.

You should see a list containing gpt-4.1, gpt-4.1-mini, and text-embedding-3-small.

2. Install and Run NLWeb Locally

Now we’re ready to set up a dev instance of NLWeb and get it running locally. Clone the NLWeb GitHub repository either via your favourite IDE or by using the command line:

git clone https://github.com/nlweb-ai/NLWeb.git
cd NLWeb

You’ll see the following output in the console, and the project will be placed in a new directory called NLWeb:

Windows Command Prompt showing a git clone command cloning the NLWeb repository from GitHub for setup in a local AI chat project using NLWeb, Azure AI Search, and Azure AI Foundry.

Download Python

Visit the Python project page and download the latest version. After launching the installer, tick Add Python to your environment variables so that it’s accessible globally via the command line:

Python 3.14 setup window showing advanced installation options selected, including adding Python to environment variables for configuring a local AI chat project using NLWeb, Azure AI Search, and Azure AI Foundry.

If you’re using the Python install manager instead, a similar option will appear in the console.

Once installed, open a brand new console window and make sure you’re good to go by typing py or python and pressing enter. You should see the following output:

Windows Command Prompt showing Python 3.14 successfully launched, confirming installation for use in a local AI chat project using NLWeb, Azure AI Search, and Azure AI Foundry.

If you see “'python' is not recognised as an internal or external command“, double check that it’s been added to your PATH variable (how to).

Install NLWeb Dependencies

From the new NLWeb directory, run the following commands in turn:

# Create virtual environment
python -m venv myenv

# Activate the virtual environment
myenv\Scripts\activate

# Navigate to the Python code folder
cd code\python

# Install dependencies
pip install -r requirements.txt

3. Configure NLWeb for Azure Integration

We’ll need to make some changes to the default configuration files in NLWeb to get everything up and running. Here’s what changes you need to make to files in the config directory, assuming you followed everything so far in this demo:

config_conv_store.yaml

Change the default storage endpoint from qdrant_local to azure_search:

# Default storage endpoint to use
default_storage: azure_search

config_embedding.yaml

Update the api_version_env to reflect the API version you are using:

api_version_env: "2024-02-01"  # Example date - replace with your specific version

You can find the API version your models are using by viewing the details in AI Foundry. Selecting gpt-4.1, for example, shows an initial Get Started page, which reveals the version:

Azure AI Foundry deployment page showing authentication setup for the gpt-4.1 model using an API key and endpoint, as part of configuring a local AI chat project with NLWeb, Azure AI Search, and Azure AI Foundry.

config_retrieval.yaml

Switch out the write_endpoint value at the top of the file from qdrant_local to azure_ai_search:

write_endpoint: azure_ai_search

This tells NLWeb to write embeddings and metadata directly into Azure Search instead of the default local Qdrant vector database.

Next, a few lines below under the azure_ai_search heading, change the index_name to nlweb:

azure_ai_search:
  ...
  index_name: nlweb

Let’s also turn off some of the real time web search APIs and local storage options whilst we’re here, as we won’t use them for now:

bing_search:
  enabled: false
  
...

qdrant_local:
  enabled: false
  
...

shopify:
  enabled: false

That’s it – we’re done with config updates for this demo, though do have a trawl through and examine some of the options that can be set, as they might come in useful for your own projects.

Creating Environment File: .env

There’s one final thing to do before we get ready to run NLWeb locally, and that’s to ensure we provide it with all of the required environment variables. This is done in Python by creating a .env file. Kindly, a template has been provided with the repository, named .env.template, which contains lots of flags that can be set to configure different behaviours.

Create a new file called .env in the root NLWeb directory you pulled using git earlier. The only settings we require for our setup in the file are below:

AZURE_VECTOR_SEARCH_ENDPOINT="https://YOUR-SEARCH-SERVICE-NAME.search.windows.net"
AZURE_VECTOR_SEARCH_API_KEY="YOUR-KEY"

AZURE_OPENAI_ENDPOINT="https://YOUR-AI-FOUNDRY-NAME.cognitiveservices.azure.com/"
AZURE_OPENAI_API_KEY="YOUR-AI-FOUNDRY-KEY"

NLWEB_LOGGING_PROFILE=development

Remember to replace both endpoint addresses and secrets with your own. These settings can be found in the Azure search service you created:

  • Endpoint address is the Url field in the Overview tab.
  • Key can be found by selecting Settings > Keys in the left-hand menu.

In AI Foundry, both are on the Overview page, labelled Azure AI Foundry project endpoint and API Key.

Remember, don’t ever commit or share your .env file as it contains sensitive information – always add it to .gitignore.

Check Connectivity

Once you’ve made all the required configuration updates, it’s time to ensure that connectivity to the search service is working as expected by running the following command:

# Run the check_connectivity.py script
python testing/check_connectivity.py

At this point, an index doesn’t yet exist in the search service because we’ve not loaded any data. It’s normal to see just two out of the three connections in a successful state at this point:

Windows Command Prompt showing Azure AI Search connection test results, indicating a missing index for nlweb-search-service while confirming successful Azure OpenAI connectivity for a local AI chat setup using NLWeb, Azure AI Search, and Azure AI Foundry.

You’ll notice the message "The index 'nlweb' for service 'nlweb-search-service' was not found." Don’t worry about this failed connection for now – we’ll create the index in the next step by loading in some sample data I’ve curated.

4. Ingest and Verify the Dog Breed Dataset

There are a few ways to build indexes in the Azure search service, but for this demo we’ll keep things simple and use the packages tool data_loading.db_load that comes with NLWeb. It you look in \data\json directory of the NLWeb repo you’ll see a pre-packaged example dataset called scifi_movies_schemas.txt – I’ve used that as an example to create some structured dog breed data (in this case non-schema.org aligned) to demo how easy it is to pull in and make usable.

Building the Dataset

I’ve created a small knowledge base using factual labels (not prose) from The Kennel Club’s A-Z list. The structured JSON file describes the breed, its characteristics, and the source of the data. The data_loading.db_load tool is very particular about formatting, so I’ve made sure to follow the example’s structure to keep things simple – although, in reality, it can accept multiple data formats.

Here’s what the import file looks like in full:

Notepad++ window displaying dog_breeds.txt file containing JSON-formatted URLs and metadata for dog breeds, used as sample data ingestion for a local AI chat project with NLWeb, Azure AI Search, and Azure AI Foundry.

Expanding and formatting one of those lines, you can see exactly what I’ll be ingesting into the new search index:

  https://www.thekennelclub.org.uk/search/breeds-a-to-z/breeds/toy/affenpinscher/
{
	"@id": "affenpinscher",
	"name": "Affenpinscher",
	"group": "toy",
	"imageUrl": "https://www.thekennelclub.org.uk/media/1598/affenpinscher.jpg?rmode=crop&ranchor=center&width=81&height=81&v=1d8265fb5361a40",
	"imageAttribution": "Image (thumbnail) © The Kennel Club",
	"imageSource": "https://www.thekennelclub.org.uk/search/breeds-a-to-z/breeds/toy/affenpinscher/",
	"source": "https://www.thekennelclub.org.uk/search/breeds-a-to-z/",
	"characteristics": {
		"size": "small",
		"exercise_minutes_per_day": {
			"min": null,
			"max": 60
		},
		"home_size": "apartment",
		"grooming": {
			"times_per_week": 3,
			"note": "2-3x/week"
		},
		"coat_length": [
			"medium"
		],
		"sheds": true,
		"lifespan_years": {
			"min_years": 12,
			"max_years": null
		},
		"vulnerable_native_breed": false,
		"town_or_country": "either",
		"garden_size": "small_medium"
	}
}

Download the file in full from this demo project’s GitHub repo: dog_breeds.txt.

Note: If you’re working with a pretty small dataset, AI can help condense your content into smaller, structured pieces like this. It’s often easier than screen-scraping the information yourself, and really handy for testing out small projects like this.

How to Ingest Data

Using default settings, the data_loading.db_load tool will create a new index (if one doesn’t already exist) using the index_name we specified in the config_retrieval.yaml file earlier:

azure_ai_search:
  enabled: true
  api_key_env: AZURE_VECTOR_SEARCH_API_KEY
  api_endpoint_env: AZURE_VECTOR_SEARCH_ENDPOINT
  index_name: nlweb
  db_type: azure_ai_search

Copy the dog_breeds.txt data file to the data\json folder:

Windows File Explorer showing the json data folder in the NLWeb project containing dog_breeds.txt and scifi_movies_schemas.txt, used for data ingestion in a local AI chat setup with NLWeb, Azure AI Search, and Azure AI Foundry.

Run the following commands to ingest the data into the search service:

# Use the virtual environment
python -m venv myenv
myenv\Scripts\activate

# Change directory to code\python
cd code\python

# Load the data into the search service
python -m data_loading.db_load ..\..\data\json\dog_breeds.txt DogBreeds

Once the script has run to completion, you’ll see a status update in the console:

Windows Command Prompt showing successful upload of 25 documents to Azure AI Search, confirming data ingestion completion for a local AI chat project using NLWeb, Azure AI Search, and Azure AI Foundry.

Navigate to the search service and you should see statuses updated to reflect the new data:

Azure Portal showing the nlweb-search-service index details with 225 documents successfully ingested, confirming Azure AI Search setup for a local AI chat project using NLWeb, Azure AI Search, and Azure AI Foundry.

Clicking into the nlweb index and pressing the Search button will return all of the loaded data. Note the site value that we specified in the db_load command:

Azure Portal Search Explorer showing indexed JSON data from the nlweb-search-service, confirming successful ingestion of dog breed information for a local AI chat project using NLWeb, Azure AI Search, and Azure AI Foundry.

Selecting the Fields tab in this view, checking the embedding as retrievable, saving and reloading the data in the Search explorer tab will allow you to see the vectors created in the embedding field:

Azure Portal Search Explorer displaying detailed JSON data for an indexed dog breed entry, including URL, name, site, schema, and embedding vector, as part of a local AI chat project using NLWeb, Azure AI Search, and Azure AI Foundry.

That’s where the embedding model that we deployed earlier, text-embedding-3-small, has first come into play. A small embeddings model is perfect for our single domain set of dog breed data, though more complex models exist for multi-domain datasets. I won’t cover that here, but there’s a great OpenAI article on text embeddings, if you want to read more.

Remember to mark the embedding field as non-retrievable again so that it’s not returned to the client with every request. If you leave it on, it could negatively impact performance in your searches.

Hybrid Search Explained

Azure AI Search combines BM25 keyword scoring with vector similarity to return both literal matches (“small dog apartment”) and semantic ones (“low-energy dogs for city life”). In this demo, you’ll see how embeddings add nuance beyond plain text matches.

How to Delete Data

The data_loading.db_load tool has some useful flags you can set when running, not least the one to delete specific portions of data from the search service. There are situations where you may have data in a search index that belongs to different ‘sites’ – for example, if you’ve imported any of the other sample data like Behind-The-Tech from the NLWeb GitHub Hello World doc. In that case, to delete only a specific set of imported data with a given site name, run the following command:

python -m data_loading.db_load --delete-site DogBreeds --only-delete

The console will report “Successfully deleted 225 documents for site: DogBreeds” and leave data for other sites intact within the search index.

Re-running Connectivity Checks

Following the steps above, which created our search index, let’s re-run the connectivity checks for NLWeb. All connections should now be working, indicating that the environment is configured correctly:

Windows Command Prompt showing all three connections successful for embedding, retriever, and LLM APIs, confirming full environment configuration for a local AI chat project using NLWeb, Azure AI Search, and Azure AI Foundry.

5. Start NLWeb and Chat with Your Data

We have everything in place now to successfully start a local NLWeb instance and begin interacting with our data. For subsequent runs that don’t require initial installation of packages, I’ve found it useful to create a small batch file in Windows to avoid typing out commands each time I want to start the NLWeb server. Save the following to a file called startup_win.bat in the NLWeb directory and double click the file:

# Enter virtual environment
call myenv\Scripts\activate

# Navigate to the Python code folder
cd code\python

# Open Chrome on localhost (you can change this to whichever browser you prefer)
start "chrome.exe" http://localhost:8000/

# Run the app file to start the server
python app-file.py

If all went well, you should see the following in your command prompt:

Windows Command Prompt showing NLWeb server startup logs with WebSocket connection requests, confirming the local AI chat system is running and accepting client connections using NLWeb, Azure AI Search, and Azure AI Foundry.

And a Chrome browser (or whichever you chose) will launch with the out-of-the-box chat interface. Below, you can see I’ve been asking some questions, trying to choose the perfect dog to my pretend couch-potato, apartment lifestyle…

NLWeb Chat interface running locally in a browser showing an AI-generated response about small, non-shedding dogs suitable for apartments, demonstrating a working local AI chat using NLWeb, Azure AI Search, and Azure AI Foundry.

Try out some of your own queries and experience the cool features of contextualisation that NLWeb makes easy!

What’s Next

We’ve now got a fully working local instance of NLWeb connected to Azure AI Search and Azure AI Foundry – complete with a searchable dog breed knowledge base and semantic vectors. That’s a powerful foundation for any conversational web app.

In part two (coming soon), we’ll take this even further by surfacing results through a simple .NET 10 front end to show how easy it is to plug into the NLWeb API endpoints.

If you want to get ahead while you wait for part two, check out these official resources:

That’s it for now – see you next time!

Leave a Reply

Your email address will not be published. Required fields are marked *