Build a LabVIEW AI Advisor with Claude
Build a domain-specific AI advisor for LabVIEW using Claude API and RAG.
Introduction
30 Second Summary
You have probably noticed that AI assistants like ChatGPT give generic answers when you ask highly specific technical questions about a tool you use every day. Domain-specific AI copilots solve this by connecting a general-purpose model to the actual documentation and knowledge base of that tool.
In this project, you will build a command-line LabVIEW AI advisor using the Claude API and watch it evolve from a generic assistant into a documentation-grounded domain expert, replicating the architecture behind NI's Nigel AI.
What You'll Build
You will chat with your advisor in the terminal, ask real LabVIEW questions, and see it transform from giving plausible-sounding guesses to delivering specific, documentation-backed answers as you add retrieval capabilities layer by layer.
By the end of this project, you'll have:
- A working CLI chatbot that answers LabVIEW and test-measurement questions using NI-specific terminology, referencing VIs, DAQmx functions, and hardware families.
- A RAG-enhanced mode where your advisor retrieves relevant NI documentation before answering, delivering grounded specifics instead of generic guesses.
- A comparison mode you can run to see the same question answered with and without documentation access side by side, proving exactly how much retrieval improves quality.
- Secret Mission: Add conversation memory so your advisor remembers context across multiple questions, replicating how Nigel maintains project awareness.
Are there any prerequisites?
You need Python installed on your machine and basic familiarity with running Python scripts. You will also create an Anthropic Console account (separate from a Claude Pro subscription) for API access.
Before We Start
NI's Nigel AI can analyze VIs, suggest code, and retrieve documentation because it combines a general-purpose LLM with domain knowledge and document retrieval. In this project, you will build the same architecture from scratch using the Claude API and Python.
Before you write any code, you need an Anthropic Console account for API access, a project folder with the right packages installed, and an environment file to store your key safely.
In this step, get ready to:
- Create an Anthropic Console account and generate an API key.
- Set up a Python project folder with a virtual environment and required packages.
- Configure a .env file to store your API key securely.
Create your Anthropic Console account and API key
The Anthropic Console is the developer platform where you create API keys to call Claude programmatically. This is separate from a claude.ai Pro subscription.
- Open your browser and navigate to console.anthropic.com.
- Sign up for a new account (or sign in if you already have one).
- Once signed in, click Settings in the left sidebar, then click API keys.
- Click Create key and give it a name like labview-advisor.
- Copy the key immediately and save it somewhere safe (a notes app or password manager).
Copy your key now
The Console shows the full key only once at creation. If you close the dialog without copying it, you cannot view it again and will need to create a new one.
How much does the API cost?
The Anthropic Console uses pay-as-you-go billing. For this project you will make roughly 50 to 100 API calls during development. Expect to spend $0.10 to $0.50 total.
Set up your project folder and install packages
You need three Python packages for this project: anthropic for calling the Claude API, chromadb for storing and searching documents locally, and python-dotenv for loading environment variables from a file.
- Open your terminal.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir labview-advisor
cd labview-advisor
- Create and activate a virtual environment by running:
macOS
python3 -m venv .venv
source .venv/bin/activate
Windows
python -m venv .venv
.venv\Scripts\activate
You should see (.venv) appear at the start of your terminal prompt. This confirms the virtual environment is active.
- Install the three project packages by running:
pip install anthropic chromadb python-dotenv
What does this command do?
- anthropic is the official Python SDK for calling the Claude API.
- chromadb is a local vector database you will use later for document retrieval (RAG).
- python-dotenv loads environment variables from a .env file so you never hardcode secrets in your code.
The install prints download progress and finishes with a line like Successfully installed anthropic-... chromadb-... python-dotenv-....
Seeing an error during install?
- Make sure your virtual environment is active. You should see (.venv) in your prompt.
- If you see a permissions error, confirm you are inside the virtual environment rather than installing globally.
- ChromaDB downloads a small embedding model (~80MB) on first use. If the install hangs, check your internet connection.
Still stuck? Help me fix a pip install error for anthropic, chromadb, or python-dotenv in my virtual environment.
Create your environment file
Your API key needs to stay out of your code so it is never accidentally shared. A .env file keeps the key in one place, and python-dotenv loads it at runtime.
- In your code editor, create a new file called .env in the labview-advisor folder.
- Paste the following into .env, replacing your-api-key-here with the real key you copied earlier:
ANTHROPIC_API_KEY=your-api-key-here
What does this file do?
When your Python script calls load_dotenv(), it reads .env and makes ANTHROPIC_API_KEY available through os.getenv(). This keeps the key out of your source code.
- Save .env.
- Confirm the file exists by running:
ls -a
You should see .env and .venv listed in the output.
✔️ Awesome, I've got everything!
Great. Double check you have saved your .env file and that it contains your real API key (not the placeholder text).
ⓧ I'd like to double check the full code
Your .env file should look exactly like this (with your real key replacing the placeholder):
ANTHROPIC_API_KEY=your-api-key-here
Don't see .env in the file list?
- Files starting with a dot are hidden by default. Make sure you used ls -a (with the -a flag) to show hidden files.
- On Windows, use dir /a instead to see hidden files.
- Confirm you created the file inside the labview-advisor folder, not one level above it.
Need help? Help me find my .env file in the labview-advisor project folder.
Your environment is ready and your API key is stored safely. Next up, you will write your first Claude API call and watch it respond to a LabVIEW question in the terminal.
Build the Basic LabVIEW Advisor
In Step 1, you got your environment ready with packages installed and your API key configured. Now it is time to build the actual advisor.
A raw Claude API call gives generic answers to any question. Nigel's first layer of specialization is its domain-expert system prompt that teaches the LLM NI-specific knowledge and response patterns. You will build this same layer and see how much improvement a well-crafted prompt provides.
In this step, get ready to:
- Craft a LabVIEW domain system prompt that specializes Claude for test-and-measurement workflows.
- Build an interactive chat loop with message history and helper functions.
- Run the advisor and ask LabVIEW questions to observe domain-specific responses.
Craft the LabVIEW domain system prompt
A system prompt is a set of instructions you give the LLM before it sees the user's message. It shapes how the model responds to every question in the conversation.
- Create a new file called advisor.py in your project folder and add the following setup code:
import os
from dotenv import load_dotenv
from anthropic import Anthropic
import chromadb
# Load environment variables from .env file
load_dotenv()
# Initialize the Anthropic client with your API key
client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
# Domain-expert system prompt that specializes Claude for LabVIEW
SYSTEM_PROMPT = """You are a LabVIEW and test-measurement AI advisor, similar to NI's Nigel AI. You specialize in:
- NI LabVIEW graphical programming (VIs, SubVIs, block diagrams, front panels)
- Data flow programming concepts and best practices
- NI hardware families: DAQ (cDAQ, USB-DAQ), PXI, GPIB, modular instruments
- NI TestStand test management and sequencing
- Measurement concepts: signal conditioning, sampling rates, triggering, timing
- Common LabVIEW patterns: producer/consumer, state machines, queued message handlers
- NI software ecosystem: LabVIEW, TestStand, FlexLogger, InstrumentStudio, VeriStand
When answering:
- Use NI-specific terminology (VIs, controls, indicators, wires, tunnels, shift registers)
- Reference specific LabVIEW palettes and function categories when relevant
- Provide practical examples using LabVIEW concepts
- If you are unsure about a specific function name or path, say so rather than guessing
- Format code-related answers assuming graphical LabVIEW unless the user specifies text-based code
"""
What does this code do?
The imports bring in the tools your advisor needs: anthropic for calling Claude, chromadb for document retrieval (used later), and python-dotenv for loading your API key securely.
The SYSTEM_PROMPT teaches Claude to behave like a LabVIEW specialist. It covers NI hardware families, common design patterns like state machines and producer/consumer, and instructs Claude to use NI-specific terminology like VIs and shift registers.
- Save advisor.py.
- Confirm the file exists by running:
ls advisor.py
You should see advisor.py listed in the output.
File not showing up?
Make sure you saved the file inside your project folder (the same folder where your .env file lives). Run ls to see all files in your current directory and confirm you are in the right place.
Help me find my project folder and confirm advisor.py is saved correctly.
Build the interactive chat loop
The system prompt tells Claude what it knows. Now you need the machinery that actually sends user questions to Claude and prints the answers back in a loop.
- Add the following helper function below your SYSTEM_PROMPT in advisor.py:
def get_rag_context(query, collection):
"""Retrieve relevant documentation chunks for a query."""
results = collection.query(
query_texts=[query],
n_results=3
)
if results["documents"] and results["documents"][0]:
context_parts = []
for doc in results["documents"][0]:
context_parts.append(doc)
return "\n\n---\n\n".join(context_parts)
return ""
What does this function do?
This function queries a ChromaDB collection for document chunks related to the user's question. It returns the top 3 most relevant passages joined together. You will load actual documents into ChromaDB in a later step. For now, this function is ready and waiting.
- Add this next function directly below get_rag_context:
def build_messages_with_context(user_input, history, collection, use_rag):
"""Build the message list, optionally including RAG context."""
messages = list(history)
if use_rag:
rag_context = get_rag_context(user_input, collection)
if rag_context:
augmented_input = (
f"Relevant NI documentation:\n\n{rag_context}\n\n"
f"---\n\nUser question: {user_input}"
)
else:
augmented_input = user_input
else:
augmented_input = user_input
messages.append({"role": "user", "content": augmented_input})
return messages, user_input
What does this function do?
This function builds the message list for each API call. When RAG is enabled, it prepends retrieved documentation to the user's question so Claude can reference real NI docs. When RAG is off (the current mode), it passes the question through unchanged.
- Now add the main chat function that ties everything together. Add this below build_messages_with_context:
def chat(use_rag=False):
"""Main chat loop for the LabVIEW advisor."""
chroma_client = chromadb.PersistentClient(path="./chroma_db")
try:
collection = chroma_client.get_collection(name="ni_docs")
rag_available = True
except Exception:
rag_available = False
collection = None
if use_rag and not rag_available:
print("Warning: RAG requested but no documents loaded.")
print("Run 'python load_docs.py' first to load documentation.")
print("Falling back to system-prompt-only mode.\n")
use_rag = False
mode = "RAG Enhanced (documentation access enabled)" if use_rag else "System Prompt Only (no documentation access)"
print(f"\n{'='*60}")
print(f"LabVIEW AI Advisor - Mode: {mode}")
print(f"{'='*60}")
print("Type 'quit' to exit, 'compare' to see both modes side-by-side.\n")
history = []
What does this setup do?
The function initializes a ChromaDB client and tries to find a document collection called ni_docs. Since you have not loaded documents yet, the except block will catch the error and set rag_available to False.
The history list stores the conversation so Claude remembers earlier messages within the same session.
- Add the while loop directly below history = [] (still inside the chat function, indented one level):
while True:
user_input = input("You: ").strip()
if not user_input:
continue
if user_input.lower() == "quit":
print("Goodbye!")
break
if user_input.lower() == "compare":
compare_mode(collection if rag_available else None)
continue
messages, original_input = build_messages_with_context(
user_input, history, collection, use_rag
)
response = client.messages.create(
model="claude-sonnet-4-5-20250514",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=messages
)
assistant_reply = response.content[0].text
print(f"\nAdvisor: {assistant_reply}\n")
history.append({"role": "user", "content": original_input})
history.append({"role": "assistant", "content": assistant_reply})
What does this loop do?
Each iteration reads input from the terminal, builds the message list using build_messages_with_context, and sends it to claude-sonnet-4-5-20250514 with your domain system prompt.
After each response, both the user message and assistant reply are appended to history so the next turn has the full conversation context. Typing quit exits the loop.
- Add the comparison function below the chat function (back at the top indentation level):
def compare_mode(collection):
"""Compare responses with and without RAG for the same question."""
print("\n--- COMPARISON MODE ---")
question = input("Enter a LabVIEW question to compare: ").strip()
if not question:
return
print(f"\n{'='*60}")
print("WITHOUT RAG (System Prompt Only):")
print(f"{'='*60}")
response_no_rag = client.messages.create(
model="claude-sonnet-4-5-20250514",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": question}]
)
print(response_no_rag.content[0].text)
- Continue the compare_mode function by adding this code directly below the last print statement (still inside the function):
if collection:
print(f"\n{'='*60}")
print("WITH RAG (Documentation Enhanced):")
print(f"{'='*60}")
rag_context = get_rag_context(question, collection)
augmented_question = (
f"Relevant NI documentation:\n\n{rag_context}\n\n"
f"---\n\nUser question: {question}"
)
response_rag = client.messages.create(
model="claude-sonnet-4-5-20250514",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": augmented_question}]
)
print(response_rag.content[0].text)
else:
print("\n(RAG comparison unavailable - run load_docs.py first)")
print()
What does compare mode do?
When you type compare in the chat, this function asks the same question twice: once without documentation and once with. Since you have not loaded documents yet, it will show only the no-RAG response for now. You will see the full comparison working in a later step.
- Finally, add the entry point at the very bottom of advisor.py (at the top indentation level, below compare_mode):
if __name__ == "__main__":
import sys
use_rag = "--rag" in sys.argv
chat(use_rag=use_rag)
What does this block do?
This is Python's standard entry point. It checks for a --rag flag when you run the script. For now you will run without the flag, so the advisor uses the system prompt only.
- Save advisor.py.
Before you run this, what do you think will happen when you ask a LabVIEW question? Will it sound like a generic AI, or will the system prompt make a difference?
- Run the advisor by executing:
python advisor.py
You will see the header banner, then a You: prompt waiting for input. Type the following question:
How do I read data from a DAQ device in LabVIEW?
The advisor responds with a LabVIEW-specific answer referencing VIs, DAQmx functions, and NI terminology. Type quit to exit.
Seeing an authentication error?
If you see AuthenticationError, your API key is not loading correctly. Check that your .env file contains ANTHROPIC_API_KEY= followed by your actual key with no spaces around the equals sign.
If you see a ModuleNotFoundError, make sure your virtual environment is activated before running the script.
Help me debug my advisor.py authentication or import error.
✔️ Awesome, I've got everything!
Great. Double check you have saved advisor.py and the chat responded with LabVIEW-specific terminology.
ⓧ I'd like to double check the full code
Your advisor.py contains several functions. Scroll through and confirm you have each of these sections in order:
- Imports and client initialization at the top.
- SYSTEM_PROMPT multi-line string.
- get_rag_context() function.
- build_messages_with_context() function.
- chat() function containing the ChromaDB setup and while True loop.
- compare_mode() function.
- if __name__ == "__main__" block at the bottom.
Your advisor already sounds like a LabVIEW specialist thanks to the system prompt. Next, you will push it harder with specific technical questions to expose where it falls short without documentation access.
Challenge the Advisor and Expose Its Limits
Your advisor sounds impressive when you ask general LabVIEW questions. But sounding knowledgeable and being knowledgeable are different things.
The system prompt teaches Claude how to talk like a LabVIEW expert, but it cannot access actual NI documentation. Ask for a specific function name, an exact VI path, or a precise hardware configuration detail, and you will see the cracks. This is exactly the limitation that Nigel solves by connecting to NI's knowledge base.
In this step, get ready to:
- Ask the advisor questions that require exact NI function names and hardware details.
- Observe where it hedges, guesses, or produces confident-but-unverifiable answers.
- Note 2-3 specific questions that expose its limits for comparison later.
Test with specific technical questions
General questions like "What is LabVIEW?" let the advisor coast on its system prompt. To expose the real limitation, you need questions that demand specifics only found in actual documentation.
- Start your advisor by running this command:
python advisor.py
You should see the header displaying the current mode:
============================================================
LabVIEW AI Advisor - Mode: System Prompt Only (no documentation access)
============================================================
Type 'quit' to exit, 'compare' to see both modes side-by-side.
Why does the mode header matter?
The mode header tells you exactly what the advisor has access to right now. "System Prompt Only (no documentation access)" means Claude is relying entirely on its general training data and the system prompt instructions. No NI documentation is being retrieved.
- Type this question into the chat and press Enter:
What is the exact function name to configure a cDAQ timing source?
Read the response carefully. Does it give you a precise, verifiable function name? Or does it give something that sounds plausible but you cannot confirm?
- Now try a question about a specific VI path:
What is the exact palette path to find the DAQmx Create Virtual Channel VI in LabVIEW?
- Try one more question about hardware configuration specifics:
What are the exact property names I need to set for a hardware-timed continuous analog input task on a cDAQ-9174?
What patterns are you seeing?
You will likely notice a few telltale signs. The advisor might use hedging language like "typically" or "you would generally use." It might give a function name that sounds correct but is slightly off or impossible to verify without documentation.
This is the fundamental limitation of an LLM without access to source documentation. It has learned patterns about LabVIEW from its training data, but it cannot look up the exact, current details.
Document the gap for later comparison
You are about to add documentation retrieval in the next step. To see the improvement clearly, you need a baseline. Pick 2-3 questions where the advisor's answers felt weak or unverifiable.
- Note down 2-3 questions from your conversation where the advisor produced hedging or unverifiable specifics.
- Keep these questions handy. You will re-ask them once RAG is enabled to see the difference.
- Type quit to exit the advisor when you are done testing.
How does Nigel solve this?
NI's Nigel AI is embedded in LabVIEW, TestStand, FlexLogger, InstrumentStudio, and VeriStand. It connects to NI's documentation and knowledge base so that when you ask for a specific function name, it retrieves the answer from official sources rather than guessing.
That retrieval layer is exactly what you will build next using ChromaDB. The architecture is the same: LLM plus domain prompt plus document retrieval.
Before you move on, think about this: if you asked the same cDAQ timing question to an advisor that had the full NI documentation in front of it, what would you expect the answer to contain?
What should a documentation-grounded answer look like?
A grounded answer would name the exact DAQmx VI (like DAQmx Timing), describe its specific inputs, and reference which palette it lives in. No hedging. No "typically." The answer either matches the documentation or it does not.
You have seen the gap firsthand. The system prompt makes Claude talk like a LabVIEW expert, but it cannot provide verifiable specifics without access to actual documentation. Next up, you will close that gap by loading NI documentation into a vector database and retrieving relevant passages before each answer.
Add RAG with NI Documentation
In Step 3, you saw the advisor hedge or give generic answers when asked about specific NI function names and exact procedures. The system prompt teaches Claude the right terminology, but it cannot teach it details that live only in NI documentation.
This step adds the architectural core of how Nigel works. You'll load NI documentation into a vector database and retrieve relevant passages before each answer. This pattern is called Retrieval-Augmented Generation (RAG).
In this step, get ready to:
- Load sample NI documentation into a ChromaDB vector database.
- Run the advisor in RAG mode and verify documentation-grounded answers.
- Compare RAG-enhanced vs system-prompt-only answers side by side.
Create and load the documentation knowledge base
Your advisor needs a knowledge base of NI documentation to retrieve from. You'll write a script that creates sample documentation files, splits them into searchable chunks, and loads them into ChromaDB.
- Create a new file called load_docs.py in your project folder.
- Add the imports, ChromaDB client setup, and the text chunking function by pasting this code:
import chromadb
import os
# Connect to a persistent local vector database
chroma_client = chromadb.PersistentClient(path="./chroma_db")
# Clear any existing collection and start fresh
try:
chroma_client.delete_collection(name="ni_docs")
except ValueError:
pass
collection = chroma_client.create_collection(name="ni_docs")
DOCS_DIR = "./docs"
def chunk_text(text, chunk_size=500, overlap=50):
"""Split text into overlapping chunks."""
words = text.split()
chunks = []
start = 0
while start < len(words):
end = start + chunk_size
chunk = " ".join(words[start:end])
chunks.append(chunk)
start = end - overlap
return chunks
What does this code do?
PersistentClient creates a ChromaDB database stored on disk at ./chroma_db. This means your embedded documents persist between script runs.
The chunk_text function splits long documents into overlapping 500-word pieces. Overlapping chunks ensure that context at chunk boundaries is not lost during retrieval.
- Below the chunk_text function, add the load_documents function that reads files from the docs/ directory and loads them into ChromaDB:
def load_documents():
"""Load all .txt files from the docs directory into ChromaDB."""
if not os.path.exists(DOCS_DIR):
os.makedirs(DOCS_DIR)
create_sample_docs()
doc_id = 0
documents = []
ids = []
metadatas = []
for filename in os.listdir(DOCS_DIR):
if filename.endswith(".txt"):
filepath = os.path.join(DOCS_DIR, filename)
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
chunks = chunk_text(content)
for chunk in chunks:
documents.append(chunk)
ids.append(f"doc_{doc_id}")
metadatas.append({"source": filename})
doc_id += 1
if documents:
collection.add(
documents=documents,
ids=ids,
metadatas=metadatas
)
print(f"Loaded {len(documents)} chunks from {len(os.listdir(DOCS_DIR))} files.")
else:
print("No documents found. Add .txt files to the docs/ directory.")
What does this code do?
This function scans the docs/ folder for .txt files, chunks each file into smaller pieces, and adds them all to the ChromaDB collection. If docs/ does not exist, it calls create_sample_docs() to generate sample documentation automatically.
ChromaDB handles embedding automatically using the Sentence Transformers all-MiniLM-L6-v2 model. You do not need a separate embedding API key.
Now add the create_sample_docs function that generates three NI documentation files covering DAQ fundamentals, LabVIEW design patterns, and TestStand concepts.
- Below load_documents, add the first part of create_sample_docs containing the DAQ documentation:
def create_sample_docs():
"""Create sample NI documentation files for demonstration."""
sample_docs = {
"daq_basics.txt": """NI DAQ (Data Acquisition) Fundamentals
DAQ Hardware Overview:
NI offers several families of DAQ hardware including cDAQ (CompactDAQ), USB-DAQ, and PCI/PCIe-DAQ.
CompactDAQ (cDAQ) is a modular system where you combine a chassis (like the cDAQ-9174) with C Series modules for different measurement types.
Key DAQ Concepts:
- Channels: Physical connections to sensors or signals. Can be analog input (AI), analog output (AO), digital input (DI), digital output (DO), or counter/timer.
- Tasks: A collection of one or more channels with timing, triggering, and other properties. In LabVIEW, you use DAQmx Create Task to create a task.
- Timing: Controls the sample rate and mode. Use DAQmx Timing to configure. Modes include Sample Clock (hardware-timed), On Demand (software-timed).
- Triggering: Start or reference triggers synchronize acquisition with external events.
Common DAQmx VIs in LabVIEW:
- DAQmx Create Task: Creates a new task
- DAQmx Create Virtual Channel: Adds a channel to a task (e.g., AI Voltage for analog input)
- DAQmx Timing: Configures sample clock rate and samples per channel
- DAQmx Start Task: Arms the task for acquisition
- DAQmx Read: Reads data from the task (returns waveform or numeric array)
- DAQmx Stop Task: Stops the running task
- DAQmx Clear Task: Releases all task resources
Typical DAQ Acquisition Pattern in LabVIEW:
1. Create Task
2. Create Virtual Channel (specify physical channel like cDAQ1Mod1/ai0)
3. Configure Timing (set rate, e.g., 1000 Hz, and samples per channel)
4. Start Task
5. Read in a loop
6. Stop Task
7. Clear Task""",
- Continue the sample_docs dictionary by adding the LabVIEW patterns documentation directly below the previous entry:
"labview_patterns.txt": """Common LabVIEW Design Patterns
State Machine:
The most common LabVIEW architecture for sequential operations. Uses a While Loop containing a Case Structure, with an enum shift register controlling which state executes next.
- States are cases in the Case Structure
- Transitions are determined by the enum value passed to the shift register
- Often combined with an Event Structure for UI handling
Producer/Consumer Pattern:
Used when data acquisition and processing happen at different rates.
- Producer Loop: Acquires data and enqueues it (uses Enqueue Element)
- Consumer Loop: Dequeues data and processes it (uses Dequeue Element)
- Connected via a Queue (created with Obtain Queue)
- Prevents data loss when processing is slower than acquisition
Queued Message Handler (QMH):
Extension of Producer/Consumer for complex applications.
- Messages contain a command string and optional data (variant)
- Message queue connects the event-handling loop to the message-processing loop
- Each message triggers specific processing in a Case Structure
Functional Global Variable (FGV):
A SubVI with an uninitialized shift register that maintains state between calls.
- Uses an action input (enum) to select get/set/initialize operations
- Non-reentrant SubVI ensures single instance
- Alternative to global variables with encapsulated access
Error Handling:
LabVIEW uses error clusters (containing status boolean, code integer, and source string).
- Wire error clusters through the error in/error out terminals of VIs
- Use Simple Error Handler or General Error Handler for display
- Merge Errors combines multiple error wires""",
- Add the final TestStand documentation entry, close the dictionary, write the files to disk, and add the __main__ block:
"teststand_overview.txt": """NI TestStand Overview
TestStand is off-the-shelf test management software for validation and production test.
Key Concepts:
- Sequence: An ordered list of steps that define a test procedure
- Step: A single operation in a sequence (call a VI, check a limit, log results)
- Sequence File: Contains one or more sequences (.seq file)
- Step Types: Action, Numeric Limit Test, String Value Test, Pass/Fail Test, Flow Control
Execution Flow:
- Main Sequence: The primary test sequence
- Setup/Cleanup: Run before and after the Main Sequence
- Process Model: Controls overall execution (initializing, running sequences, generating reports)
Code Modules:
TestStand calls external code modules written in:
- LabVIEW VIs
- C/C++ DLLs
- Python scripts
- .NET assemblies
Step Properties:
- Preconditions: Logical conditions that must be true for a step to execute
- Post Actions: What to do on pass/fail (e.g., go to step, terminate)
- Limits: Numeric high/low limits for pass/fail determination
- Looping: Repeat a step a fixed number of times or until a condition
Variables:
- Local Variables: Scoped to the current sequence
- FileGlobals: Available to all sequences in the file
- StationGlobals: Shared across all sequence files on the system
- Parameters: Passed between sequences (In, Out, InOut)"""
}
for filename, content in sample_docs.items():
filepath = os.path.join(DOCS_DIR, filename)
with open(filepath, "w", encoding="utf-8") as f:
f.write(content)
print(f"Created {len(sample_docs)} sample documentation files in {DOCS_DIR}/")
if __name__ == "__main__":
load_documents()
What documentation did you just add?
These three files mirror real NI documentation content covering DAQmx VI names and acquisition patterns, common LabVIEW architectures like state machines and producer/consumer, and TestStand sequencing concepts.
When you run load_docs.py, it creates the docs/ folder, generates these files, chunks them into ~500-word pieces, and stores the chunks in ChromaDB with their embeddings.
- Save load_docs.py.
- Run the script to populate your vector database:
python load_docs.py
The first run downloads the embedding model (~80MB). You should then see output confirming the documents were created and loaded.
- Confirm the docs/ folder was created by running:
ls docs/
You should see daq_basics.txt, labview_patterns.txt, and teststand_overview.txt listed.
Seeing an error about the embedding model?
The first time ChromaDB runs, it downloads its default embedding model. If you see a network error, check your internet connection and run python load_docs.py again.
If you see a warning about no embedding function provided, that is normal. ChromaDB defaults to the built-in Sentence Transformers model. Help me fix a ChromaDB embedding error when running load_docs.py
✔️ Awesome, I've got everything!
Great. Double check you have saved load_docs.py and confirmed the three documentation files exist in docs/.
ⓧ I'd like to double check the full code
Your load_docs.py should contain these sections in order. Scroll up and confirm each chunk is present:
- Imports, PersistentClient setup, collection creation, DOCS_DIR, and chunk_text function.
- load_documents function that reads .txt files, chunks them, and adds them to the collection.
- create_sample_docs function with three documentation entries (daq_basics.txt, labview_patterns.txt, teststand_overview.txt) and the write loop.
- if __name__ == "__main__": load_documents() at the bottom.
Run the advisor in RAG mode
Your advisor.py already has the RAG integration built in. The get_rag_context function queries ChromaDB for the top 3 most relevant chunks before each answer. Now that documents are loaded, the --rag flag activates this retrieval.
- Start the advisor in RAG mode by running:
python advisor.py --rag
You should see the header now reads Mode: RAG Enhanced (documentation access enabled) instead of the previous system-prompt-only label.
- Before you ask a question, think back to the gaps you found in Step 3. What do you expect will be different now that the advisor has documentation access?
- Ask one of the specific questions that exposed gaps earlier. For example:
What are the steps in a typical DAQmx acquisition sequence?
The response should now reference specific DAQmx VI names like DAQmx Create Task, DAQmx Timing, and DAQmx Read, pulled directly from your loaded documentation.
- Type quit to exit when you are done testing.
Why are the answers more specific now?
Before each API call, build_messages_with_context queries ChromaDB for the 3 most relevant document chunks matching your question. Those chunks are injected into the prompt as "Relevant NI documentation" before your question.
Claude can now ground its answer in the actual documentation text rather than relying on general training knowledge. This is exactly how Nigel retrieves from NI's 50+ years of documentation.
Still seeing generic answers?
Make sure you ran python load_docs.py successfully before starting the advisor. Check that the output said "Loaded X chunks from 3 files."
Also confirm you started the advisor with the --rag flag. Without it, the advisor runs in system-prompt-only mode. Help me troubleshoot why my RAG advisor is still giving generic answers
Compare results side by side
The advisor includes a built-in comparison mode that sends the same question through both paths and shows you both responses. This makes the RAG improvement unmistakable.
- Start the advisor again (with or without --rag):
python advisor.py --rag
- Type compare when prompted for input.
- Enter a LabVIEW question when it asks. Try something like:
How do I implement a producer/consumer pattern for continuous acquisition?
You should see two labeled sections printed one after the other: WITHOUT RAG (System Prompt Only) followed by WITH RAG (Documentation Enhanced).
The RAG version should mention specific LabVIEW elements like Enqueue Element, Dequeue Element, and Obtain Queue that appear in your loaded documentation.
- Type quit to exit.
What is the compare function doing?
The compare_mode function sends your question twice: once as a plain message (no retrieval) and once with the top 3 relevant document chunks prepended. Both use the same system prompt and model, so the only variable is whether documentation context is included.
Your advisor has evolved from a generic assistant into a documentation-grounded expert. Next, you'll put it through a structured evaluation against Nigel's claimed capabilities to understand exactly where this approach matches a commercial AI copilot and where it falls short.
Evaluate Against Nigel's Capabilities
You have a working LabVIEW advisor that answers questions using both a domain system prompt and RAG-retrieved documentation. But how does it actually compare to Nigel?
The whole point of this project is to assess whether Claude can replicate what NI's Nigel AI does. In this step, you'll build an automated evaluation that scores your advisor against Nigel's claimed capabilities and identifies exactly where a standalone API falls short.
In this step, get ready to:
- Define a test suite of questions mapped to Nigel's core capability categories.
- Run an automated evaluation that scores your advisor on domain relevance and confidence.
- Generate a scorecard comparing your advisor against Nigel's features.
Define evaluation criteria based on Nigel's capabilities
Nigel AI claims four core capability areas: VI analysis, code generation, hardware advisory, and documentation retrieval. You'll create a dedicated evaluation script that tests your advisor against each of these areas systematically.
- Create a new file called evaluate.py in your project folder by running:
touch evaluate.py
- Confirm the file exists by running:
ls
You should see evaluate.py listed alongside advisor.py and load_docs.py.
- Open evaluate.py in your code editor and add the file setup with the same imports and system prompt your advisor uses:
import os
from dotenv import load_dotenv
from anthropic import Anthropic
import chromadb
load_dotenv()
client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
SYSTEM_PROMPT = """You are a LabVIEW and test-measurement AI advisor, similar to NI's Nigel AI. You specialize in:
- NI LabVIEW graphical programming (VIs, SubVIs, block diagrams, front panels)
- Data flow programming concepts and best practices
- NI hardware families: DAQ (cDAQ, USB-DAQ), PXI, GPIB, modular instruments
- NI TestStand test management and sequencing
- Measurement concepts: signal conditioning, sampling rates, triggering, timing
- Common LabVIEW patterns: producer/consumer, state machines, queued message handlers
- NI software ecosystem: LabVIEW, TestStand, FlexLogger, InstrumentStudio, VeriStand
When answering:
- Use NI-specific terminology (VIs, controls, indicators, wires, tunnels, shift registers)
- Reference specific LabVIEW palettes and function categories when relevant
- Provide practical examples using LabVIEW concepts
- If you are unsure about a specific function name or path, say so rather than guessing
- Format code-related answers assuming graphical LabVIEW unless the user specifies text-based code
"""
Why duplicate the system prompt?
The evaluation script runs independently from advisor.py. It needs its own client and system prompt so you can run evaluations without starting the interactive chat loop.
- Below the SYSTEM_PROMPT, add the test suite that maps Nigel's four capability categories to specific test questions:
NIGEL_CAPABILITIES = {
"VI Analysis": [
"Describe what a VI does given its block diagram description",
"Suggest improvements to a LabVIEW state machine implementation",
],
"Code Generation": [
"Provide a LabVIEW approach for reading analog voltage from a cDAQ device",
"Describe how to implement a producer/consumer pattern for continuous acquisition",
],
"Hardware Advisory": [
"What NI hardware would you recommend for measuring thermocouple signals?",
"How do I configure timing for a multi-channel analog input task?",
],
"Documentation Retrieval": [
"What are the steps in a typical DAQmx acquisition sequence?",
"Explain the difference between local variables and FileGlobals in TestStand",
],
}
What does this test suite cover?
Each category maps directly to one of Nigel's advertised features. Two questions per category gives you enough signal to detect patterns without burning through excessive API calls.
The questions range from open-ended ("describe what a VI does") to specific ("what are the steps in a DAQmx sequence"). This tests both reasoning and factual recall.
- Save evaluate.py and verify the test suite loads correctly by running:
python -c "from evaluate import NIGEL_CAPABILITIES; print(list(NIGEL_CAPABILITIES.keys()))"
You should see the four capability categories printed:
['VI Analysis', 'Code Generation', 'Hardware Advisory', 'Documentation Retrieval']
Seeing an ImportError?
- Make sure you saved evaluate.py before running the command.
- Check that you're running the command from your project folder (the same directory where evaluate.py lives).
- Verify that your virtual environment is activated (the one with anthropic and chromadb installed).
Help me debug this ImportError in evaluate.py
Build and run the evaluation function
Now you need the function that iterates through each capability category, sends questions to Claude with RAG context, and scores the responses. This is the core logic that turns your test suite into a structured comparison.
- In evaluate.py, below the NIGEL_CAPABILITIES dictionary, add the first part of the evaluate() function that sets up ChromaDB and starts the category loop:
def evaluate():
"""Run evaluation against Nigel's capability categories."""
chroma_client = chromadb.PersistentClient(path="./chroma_db")
try:
collection = chroma_client.get_collection(name="ni_docs")
rag_available = True
except Exception:
rag_available = False
collection = None
print("\n" + "=" * 60)
print("EVALUATION: Claude LabVIEW Advisor vs Nigel AI Capabilities")
print("=" * 60)
results = {}
for category, questions in NIGEL_CAPABILITIES.items():
print(f"\n--- Category: {category} ---")
category_results = []
for question in questions:
print(f"\nQ: {question}")
What does this code do?
- The function connects to your existing ChromaDB collection so it can retrieve documentation for each test question.
- It gracefully handles the case where no documents are loaded, falling back to prompt-only mode.
- The outer loop iterates through each capability category, and the inner loop processes each question within that category.
- Continuing inside the inner for question loop, add the RAG retrieval, API call, and scoring logic:
if rag_available:
rag_results = collection.query(
query_texts=[question],
n_results=3
)
context = "\n\n".join(rag_results["documents"][0]) if rag_results["documents"][0] else ""
augmented = f"Relevant documentation:\n\n{context}\n\n---\n\nQuestion: {question}"
else:
augmented = question
response = client.messages.create(
model="claude-sonnet-4-5-20250514",
max_tokens=512,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": augmented}]
)
answer = response.content[0].text
print(f"A: {answer[:200]}...")
category_results.append({
"question": question,
"answer_length": len(answer),
"has_ni_terms": any(term in answer.lower() for term in ["vi", "labview", "daq", "teststand", "ni "]),
"hedges": any(phrase in answer.lower() for phrase in ["i'm not sure", "i don't have", "cannot verify"]),
})
results[category] = category_results
How does the scoring work?
Each response is scored on two dimensions. has_ni_terms checks whether the answer contains NI-specific terminology (proving domain relevance, not generic advice).
hedges detects when the model admits uncertainty. A high hedge rate signals that the question requires knowledge the model doesn't confidently have, even with RAG.
- Still inside the evaluate() function (after the loop closes), add the scorecard printing, limitations summary, and the __main__ block:
print("\n" + "=" * 60)
print("SCORECARD")
print("=" * 60)
for category, scores in results.items():
ni_relevant = sum(1 for s in scores if s["has_ni_terms"])
confident = sum(1 for s in scores if not s["hedges"])
total = len(scores)
print(f"\n{category}:")
print(f" NI-Domain Relevance: {ni_relevant}/{total}")
print(f" Confident Answers: {confident}/{total}")
print("\n" + "-" * 60)
print("LIMITATIONS (features requiring deeper integration):")
print(" - Actual VI file parsing (Nigel can read .vi binary files)")
print(" - Connected hardware detection (Nigel sees your NI MAX config)")
print(" - Real-time code completion in the LabVIEW editor")
print(" - Project-wide context awareness across multiple VIs")
print("-" * 60)
if __name__ == "__main__":
evaluate()
What does the limitations section reveal?
These four limitations are features that require deep platform integration, not just an LLM plus documentation. Nigel can parse actual .vi binary files and detect connected NI hardware because it's embedded in the LabVIEW IDE.
Your Claude-based advisor achieves the knowledge layer (advisory, documentation retrieval, pattern recommendations) but not the tool integration layer. This distinction is the architectural insight of the entire project.
- Save evaluate.py.
Before you run the evaluation, what do you think the scorecard will show? Do you expect all categories to score equally, or will some be stronger than others given the documentation you loaded?
- Run the evaluation script:
python evaluate.py
You'll see each question printed followed by a truncated answer, then a scorecard at the end showing NI-Domain Relevance and Confident Answers for each category. The script makes 8 API calls total (2 questions across 4 categories), so it takes about 20-30 seconds to complete.
What should I see in my scorecard?
You should see high NI-Domain Relevance scores (likely 2/2) across all categories because the system prompt ensures NI terminology appears in every answer. The Confident Answers score reveals which questions the model hedges on, even with RAG support.
Documentation Retrieval typically scores highest because those questions map directly to your loaded docs. Hardware Advisory may show hedging on specific hardware recommendations that require hands-on knowledge beyond what documentation provides.
Seeing an IndentationError or SyntaxError?
- The evaluate() function was added in three parts. Check that the indentation is consistent (4 spaces per level) and that the inner loop body is indented 12 spaces (3 levels deep).
- Make sure the if __name__ == "__main__": block is at the top level (no indentation), not inside the function.
- Verify that results[category] = category_results is indented at 8 spaces (inside the outer for loop, not the inner one).
Help me fix a SyntaxError in my evaluate.py file
✔️ Awesome, I've got everything!
Great. Double check you've saved evaluate.py and confirmed the scorecard prints successfully.
ⓧ I'd like to double check the full code
Your evaluate.py was built in three sections. Scroll up and confirm each one is present in this order:
- Imports, load_dotenv(), client creation, and SYSTEM_PROMPT (the file setup).
- NIGEL_CAPABILITIES dictionary with 4 categories, each containing 2 test questions.
- evaluate() function: ChromaDB setup, the category/question loop with RAG retrieval and scoring, the SCORECARD print section, and the LIMITATIONS print section.
- if __name__ == "__main__": block that calls evaluate().
You now have a structured evaluation proving exactly what a Claude-based advisor can and cannot do compared to Nigel. The knowledge layer (domain advisory, documentation retrieval, pattern recommendations) is fully achievable via API. The platform integration layer (VI parsing, hardware detection, editor embedding) requires the kind of deep tool access that only comes from being built into LabVIEW itself.
Secret mission
Add Conversation Memory and Context Window Management
Your advisor answers each question in isolation. It has no memory of what you asked two minutes ago. Real AI copilots like Nigel maintain project-aware context across an entire session. Can you give your advisor the same ability?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses pay-as-you-go API billing, so you only incur costs when you actively make API calls.
Resources you used:
- Anthropic Console account with API key (pay-as-you-go billing).
- Local project folder containing Python files, documentation, and ChromaDB data.
Keep everything running
No action needed. Choose this if you plan to keep experimenting with your LabVIEW advisor or adding more documentation to the knowledge base.
- Your API key stays active. You only pay when you make API calls, so there are no idle charges.
- Your local project folder, ChromaDB database, and documentation files remain on your machine.
- You can re-run the advisor any time with python advisor.py or python advisor.py --rag.
Pause - I'll come back to this later
No action needed for this project. The Claude API is pay-per-use with no idle charges. Your project files sit on disk with no ongoing cost.
- Your API key remains valid but costs nothing unless you make calls.
- Your ChromaDB database and documentation persist in the project folder, ready to use when you return.
Delete - I don't want to use this again
Remove all project resources and revoke API access to prevent any future charges.
- Revoke your API key in the Anthropic Console:
- Sign in at console.anthropic.com.
- Open the API Keys page from your settings.
- Find your key in the list, click the three-dot menu beside it, and select Delete API Key.
- Delete your local project folder. On macOS or Linux, run:
cd ~/Desktop
rm -rf labview-advisor
- On Windows, run this in PowerShell instead:
cd ~\Desktop
Remove-Item -Recurse -Force labview-advisor
Nice Work!
Nice Work!
Nice work! You just built a domain-specific AI advisor that mirrors the architecture behind NI's Nigel AI.
You've learned how to:
- Build a working CLI chatbot using the Claude API with a domain-expert system prompt that specializes responses for LabVIEW and test-measurement workflows.
- Implement Retrieval-Augmented Generation using ChromaDB to ground AI responses in actual NI documentation, exposing the dramatic difference between generic answers and documentation-backed specifics.
- Evaluate your advisor against Nigel AI's capabilities and identify which features are achievable via API and which require deeper platform integration like VI file parsing or hardware detection.
- Secret Mission: Added conversation memory with context summarization, replicating how Nigel maintains project awareness across long interactions.
Ready to quiz yourself?