The prompt behind the intent
The intent prompt is the prompt NeMo renders for the generate_user_intent task: your instructions, your examples and the conversation, ending with a request for the intent of the last user message.
Last updated: 30 Sep, 2026 · NeMo Guardrails 0.24.1
The Embedding model lesson ended with the LLM naming the intent. It does that from a prompt NeMo builds, and prompts.yml changes that prompt.
Syntax:
prompts:
- task: generate_user_intent # which prompt to replace
content: |- # a Jinja template
{{ general_instructions }}
{{ examples | verbose_v1 }}
{{ history | colang | verbose_v1 }}
output_parser: verbose_v1 # how the reply is readHow NeMo picks a prompt
NeMo ships prompt sets for model families, chosen by the model's name. openai/gpt-3.5-turbo and openai/gpt-4 get chat prompts; a name with no set of its own, such as openai/gpt-oss-120b, gets the general set, written for completion models that carry on a document.
- written in define user and define bot
- written in define user and define bot
View the code here
define user ask off topic
"tell me a joke"
"what is the capital of france"
"write me a poem"
"what is 2 plus 2"
"what should I eat for dinner"
"who won the game yesterday"
"recommend a movie"
"what is the weather like"
define bot refuse off topic
"I'm an Enterprise IT Assistant focused on Kubernetes, Intel hardware, and networking. I can't help with that — but ask me anything technical!"
define flow handle off topic
user ask off topic
bot refuse off topic
stop
from nemoguardrails.embeddings.index import EmbeddingsIndex
class EveryExample(EmbeddingsIndex):
"""Hands the model every example instead of the closest few."""
def __init__(self, **kwargs):
self.items = []
async def add_items(self, items):
self.items.extend(items)
async def build(self):
pass
async def search(self, text, max_results=5, threshold=None):
return self.items
def init(app):
app.register_embedding_search_provider("every_example", EveryExample)
models:
- type: main
engine: openai
model: openai/gpt-oss-120b
api_key_env_var: GROQ_API_KEY
parameters:
base_url: https://api.groq.com/openai/v1
temperature: 0
instructions:
- type: general
content: |
You are an Enterprise IT Assistant specialising in Kubernetes,
Intel hardware, and enterprise networking.
Only answer questions about these topics.
Answer in one or two short sentences.
core:
embedding_search_provider:
name: every_example
prompts:
- task: generate_user_intent
content: |-
"""
{{ general_instructions }}
"""
# This is how a conversation between a user and the bot can go:
{{ sample_conversation | verbose_v1 }}
# This is how the user talks:
{{ examples | verbose_v1 }}
# This is the current conversation between the user and the bot:
{{ sample_conversation | first_turns(2) | verbose_v1 }}
{{ history | colang | verbose_v1 }}
Do not answer the user. Reply with one line: the user intent of the last message.
Use an intent from the examples when one fits, otherwise write a new short intent.
output_parser: verbose_v1
The bug: the model answers instead of classifying
The topic guard with prompts.yml deleted from the folder:
from nemoguardrails import LLMRails, RailsConfig
rails = LLMRails(RailsConfig.from_path("."))
def chat(message):
reply = rails.generate(messages=[{"role": "user", "content": message}])
print("User:", message)
print("Bot :", reply["content"])
chat("Tell me a funny joke!")
print(rails.explain().colang_history)User: Tell me a funny joke! Bot : I'm an Enterprise IT Assistant focused on Kubernetes, Intel hardware, and networking. I can't help with that — but ask me anything technical! user "Tell me a funny joke!" I’m sorry, but I can only help with Kubernetes, Intel hardware, or enterprise networking topics. bot general response "I'm an Enterprise IT Assistant focused on Kubernetes, Intel hardware, and networking. I can't help with that — but ask me anything technical!"
Read the line under the user's message. Where the history should hold an intent name, it holds a sentence addressed to the user. The general prompt ends with the user's message and waits for the next line; a chat model like gpt-oss-120b treats it as a conversation and answers. No flow can match that sentence, so the turn takes the long path.
The fix
prompts.yml copies the general prompt and adds two lines at the end: do not answer the user, reply with the intent, and use an intent from the examples when one fits. It also drops the general prompt's Choose intent from this list line, which pushed every message into the only intent on the list, even on-topic questions.
Do not answer the user. Reply with one line: the user intent of the last message.
Use an intent from the examples when one fits, otherwise write a new short intent.The same message with prompts.yml
from nemoguardrails import LLMRails, RailsConfig
rails = LLMRails(RailsConfig.from_path("."))
def chat(message):
reply = rails.generate(messages=[{"role": "user", "content": message}])
print("User:", message)
print("Bot :", reply["content"])
chat("Tell me a funny joke!")
print(rails.explain().colang_history)
print(rails.explain().llm_calls[0].prompt[-230:])User: Tell me a funny joke! Bot : I'm an Enterprise IT Assistant focused on Kubernetes, Intel hardware, and networking. I can't help with that — but ask me anything technical! user "Tell me a funny joke!" ask off topic bot refuse off topic "I'm an Enterprise IT Assistant focused on Kubernetes, Intel hardware, and networking. I can't help with that — but ask me anything technical!" bot stop sed on your preferences." User message: "Tell me a funny joke!" Do not answer the user. Reply with one line: the user intent of the last message. Use an intent from the examples when one fits, otherwise write a new short intent.
What the prompt changed
- The history now holds
ask off topic, so the flow ran and the refusal came fromdefine bot. - The last 230 characters of the prompt end with the user's message and the two added lines, so the last thing the model reads is the request for an intent.
Why the video did not need this
The video's YAML names gpt-3.5-turbo as a placeholder while the real model comes from llm=. NeMo picks prompts by the name in the YAML, so the video's model got the chat prompt set, written for that name. Naming the real model in config.yml, as this course does, means choosing the prompt too.
| The general prompt set | With prompts.yml | |
|---|---|---|
| Last line the model sees | The user's message | A request for the intent |
| gpt-oss-120b replies with | An answer to the user | An intent name |
| Flows | Never match | Match |
Where you override prompts
- A model with no prompt set of its own, as here.
- The self-check prompts in the input and output rail lessons, which you always write yourself.
colang_history after changing a model.Related
- Previous: Embedding model
- Next: One model call or three
- Reference: Prompt configuration
- Print
rails.explain().llm_calls[0].promptin full and find where your examples were pasted. - Put back the Choose intent from this list line and ask What is a Kubernetes ConfigMap?.
You understood something today that you didn't yesterday.