> ## Documentation Index
> Fetch the complete documentation index at: https://docs.poly.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Tutorial: Response control

> PolyAcademy Level 2 – Use post-generation controls to fix preambles, prevent loops, and enforce brand tone.

export const LessonMeta = ({level, difficulty, time}) => {
  const levelConfig = {
    1: {
      badge: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
      label: 'Level 1'
    },
    2: {
      badge: 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200',
      label: 'Level 2'
    },
    3: {
      badge: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
      label: 'Level 3'
    }
  };
  const difficultyConfig = {
    Beginner: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
    Intermediate: 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200',
    Advanced: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
  };
  const lvl = levelConfig[level] || levelConfig[1];
  const diffColor = difficultyConfig[difficulty] || difficultyConfig['Beginner'];
  return <div className="flex flex-wrap items-center gap-2 my-4 not-prose">
      <span className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ${lvl.badge}`}>
        {lvl.label}
      </span>
      <span className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ${diffColor}`}>
        {difficulty}
      </span>
      {time && <span className="inline-flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
          <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          {time}
        </span>}
    </div>;
};

export const Quiz = ({questions = []}) => {
  const [selected, setSelected] = useState({});
  const [resetCount, setResetCount] = useState(0);
  const letters = ['A', 'B', 'C', 'D'];
  const handleSelect = (qIdx, optIdx) => {
    if (selected[qIdx] !== undefined) return;
    setSelected(prev => ({
      ...prev,
      [qIdx]: optIdx
    }));
  };
  const handleReset = () => {
    setSelected({});
    setResetCount(c => c + 1);
  };
  if (!questions?.length) return null;
  const getOptionClasses = ({hasAnswered, isThisCorrect, isThisSelected}) => {
    if (!hasAnswered) {
      return {
        btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-pointer border-gray-200 bg-white text-gray-700 hover:border-gray-300 hover:bg-gray-50 hover:shadow-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 dark:hover:border-gray-500 dark:hover:bg-gray-700',
        badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-300',
        icon: null
      };
    }
    if (isThisCorrect) {
      return {
        btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-default border-green-400 bg-green-50 text-green-900 font-medium dark:border-green-500 dark:bg-green-950 dark:text-green-100',
        badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-green-500 text-white dark:bg-green-500',
        icon: <svg className="shrink-0 w-4 h-4 text-green-500 dark:text-green-400 ml-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
          </svg>
      };
    }
    if (isThisSelected) {
      return {
        btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-default border-red-400 bg-red-50 text-red-900 dark:border-red-500 dark:bg-red-950 dark:text-red-100',
        badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-red-500 text-white dark:bg-red-500',
        icon: <svg className="shrink-0 w-4 h-4 text-red-400 dark:text-red-400 ml-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
          </svg>
      };
    }
    return {
      btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-default border-gray-100 bg-white text-gray-400 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-500',
      badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-500',
      icon: null
    };
  };
  return <div key={resetCount} className="my-6">
      {questions.map((q, qIdx) => {
    const answer = selected[qIdx];
    const hasAnswered = answer !== undefined;
    const isCorrect = answer === q.correct;
    return <div key={String(qIdx)} className="mb-8">
            <p className="flex items-start gap-2.5 font-semibold text-sm mb-3 mt-0 leading-relaxed text-gray-900 dark:text-gray-100">
              <span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-gray-800 dark:bg-gray-200 text-white dark:text-gray-900 text-xs font-bold shrink-0 mt-px leading-none">
                {qIdx + 1}
              </span>
              {q.q}
            </p>

            <div className="flex flex-col gap-2">
              {q.options.map((opt, i) => {
      const isThisCorrect = i === q.correct;
      const isThisSelected = i === answer;
      const {btn, badge, icon} = getOptionClasses({
        hasAnswered,
        isThisCorrect,
        isThisSelected
      });
      return <button key={String(i)} type="button" onClick={() => handleSelect(qIdx, i)} className={btn}>
                    <span className={badge}>{letters[i]}</span>
                    <span className="flex-1">{opt}</span>
                    {icon}
                  </button>;
    })}
            </div>

            {hasAnswered ? <div className={`mt-3 py-3 pl-4 pr-3.5 rounded-r-xl text-sm leading-relaxed border-l-4 ${isCorrect ? 'border-green-500 bg-green-50 dark:bg-green-950 dark:border-green-500' : 'border-red-500 bg-red-50 dark:bg-red-950 dark:border-red-500'}`}>
                <span className={`font-semibold ${isCorrect ? '!text-green-800 dark:!text-green-200' : '!text-red-800 dark:!text-red-200'}`}>
                  {isCorrect ? 'Correct.' : 'Not quite.'}
                </span>{' '}
                <span className="!text-gray-700 dark:!text-gray-300">{q.explanation}</span>
              </div> : null}
          </div>;
  })}

      <button type="button" onClick={handleReset} className="mt-1 text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 underline underline-offset-2 cursor-pointer transition-colors duration-150">
        Reset quiz
      </button>
    </div>;
};

export const ProgressTracker = ({lessonNum, totalLessons, level}) => {
  const [checked, setChecked] = useState(false);
  return <div onClick={() => setChecked(prev => !prev)} className={checked ? 'flex items-center gap-3 p-4 rounded-lg border-2 border-green-600 bg-green-50 dark:bg-green-950 cursor-pointer select-none transition-all' : 'flex items-center gap-3 p-4 rounded-lg border-2 border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 cursor-pointer select-none transition-all'}>
      <div className={checked ? 'w-5 h-5 rounded border-2 border-green-600 bg-green-600 flex items-center justify-center shrink-0 transition-all' : 'w-5 h-5 rounded border-2 border-gray-400 dark:border-gray-500 bg-white dark:bg-gray-800 flex items-center justify-center shrink-0 transition-all'}>
        {checked ? <svg width="10" height="8" viewBox="0 0 10 8" fill="none">
            <path d="M1 4L3.5 6.5L9 1" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
          </svg> : null}
      </div>
      <div>
        <div className={checked ? 'font-semibold text-sm text-green-700 dark:text-green-300' : 'font-semibold text-sm text-gray-700 dark:text-gray-200'}>
          {checked ? 'Lesson complete' : 'Mark lesson complete'}
        </div>
        {lessonNum && totalLessons ? <div className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
            {level ? level + ' - ' : ''}Lesson {lessonNum} of {totalLessons}
          </div> : null}
      </div>
    </div>;
};

**Level 2 – Lesson 4 of 8** – Master response controls to regulate agent output and enforce tone.

<LessonMeta level={2} difficulty="Intermediate" time="15 min" />

[Response Controls](/voice-channel/advanced/response-control) regulate the agent's *own output*. They run **after** the model generates text and **before** anything is spoken or sent. Useful for tone, safety, and flow – easy to misuse if patterns are too broad.

## What response controls are

Response Controls are **string- or regex-based rules** that inspect the agent's generated response.

<CardGroup cols={2}>
  <Card title="Halt responses" icon="octagon">
    Prevent specific phrases from being spoken
  </Card>

  <Card title="Trigger routing" icon="route">
    Activate corrective or fallback behavior
  </Card>

  <Card title="Log patterns" icon="chart-line">
    Track unwanted patterns for analysis
  </Card>

  <Card title="Enforce tone" icon="palette">
    Maintain brand voice consistency
  </Card>
</CardGroup>

Response controls are not about what the *user* says – they are about what the *agent* says.

## Which tool to use for which problem

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}}
flowchart TD
    A[Unwanted agent behavior] --> B{What type of problem?}
    B -->|ASR mishears user| C[Use ASR tools / Keyphrase Boosting]
    B -->|Wrong topic retrieved| D[Fix Managed Topic name or sample questions]
    B -->|Agent says something it shouldn't| E[Use Response Controls]
    E --> F{Should phrase NEVER appear?}
    F -->|Yes| G[Halt – set Say phrases: TRUE]
    F -->|No, just monitor it| H[Log – set Say phrases: FALSE]
    G --> I{Needs a fallback action?}
    I -->|Yes| J[Attach a function]
    I -->|No| K[Done]
```

## When to use response controls

Use response controls when you want to fix or prevent:

<Tabs>
  <Tab title="Redundant preambles">
    **Examples:**

    * "Let me check that for you…"
    * "Please hold while I look into this…"
  </Tab>

  <Tab title="Looping behavior">
    **Examples:**

    * Repeating the greeting mid-call
    * Restarting the conversation after silence
  </Tab>

  <Tab title="Off-brand phrasing">
    **Examples:**

    * Overly apologetic language
    * Internal system explanations
  </Tab>

  <Tab title="Unsafe output">
    **Examples:**

    * Speculation
    * Policy invention
    * Meta commentary about limitations
  </Tab>
</Tabs>

* If the issue is about *recognition*, use ASR tools
* If the issue is about *retrieval*, fix the FAQs
* If the issue is about *how the agent speaks*, use Response Controls

## How response controls work

<Steps>
  <Step title="LLM generates response">
    The model creates text based on context and prompts
  </Step>

  <Step title="Response Controls scan output">
    Regex patterns are evaluated against the generated text
  </Step>

  <Step title="Action taken if matched">
    * The response may be halted
    * A function may be triggered
    * The event may be logged for analytics
  </Step>
</Steps>

Controls are evaluated in real time and apply consistently across Chat and Call.

## Check your understanding

<Quiz
  questions={[
{
q: "Your agent keeps saying 'Let me check that for you' before every response, creating awkward pauses in voice calls. Which tool should you use to fix this?",
options: [
  "Managed Topic sample questions",
  "Keyphrase Boosting in Global ASR",
  "A Response Control with Say phrases set to TRUE",
  "A Transcript Correction rule",
],
correct: 2,
explanation: "This is an agent output problem – the agent is generating filler phrasing. A Response Control with 'Say phrases: TRUE' will halt the unwanted phrase before it reaches the user.",
}
]}
/>

## Core fields

Each Response Control includes:

<AccordionGroup>
  <Accordion title="ID" icon="fingerprint">
    A short, descriptive identifier used for tracking and debugging.
  </Accordion>

  <Accordion title="Description" icon="file-lines">
    Optional, but strongly recommended. Explains *why* the rule exists.
  </Accordion>

  <Accordion title="Regular Expression" icon="code">
    The exact pattern that will be matched against agent output.
  </Accordion>

  <Accordion title="Say phrases (Boolean)" icon="toggle-on">
    Despite the name, this field controls **halting**, not allowing:

    * **ON (Halt)**: matching output is **blocked** before it reaches the user
    * **OFF (Log)**: matching output is **allowed through** but recorded for analytics
  </Accordion>

  <Accordion title="Function (optional)" icon="function">
    A fallback action to run when the phrase is detected.
  </Accordion>
</AccordionGroup>

## Best practices

<CardGroup cols={2}>
  <Card title="Start narrow" icon="crosshairs">
    Broad patterns are hard to debug and can suppress valid responses.

    <Icon icon="xmark" iconType="solid" color="#dc2626" /> `.*sorry.*`

    <Icon icon="check" iconType="solid" color="#16a34a" /> `\b(sorry for the inconvenience|apologies for the delay)\b`
  </Card>

  <Card title="Target symptoms" icon="bullseye">
    Encode *specific phrases* you have observed, not abstract concepts.

    <Icon icon="xmark" iconType="solid" color="#dc2626" /> "Prevent the agent from being verbose"

    <Icon icon="check" iconType="solid" color="#16a34a" /> `\b(let me explain how|to give you some background)\b`
  </Card>

  <Card title="Prefer halting" icon="hand">
    If a phrase should *never* appear, halt it rather than replacing it.
  </Card>

  <Card title="Test thoroughly" icon="vial">
    Verify in both Chat and Call to catch unnatural truncation.
  </Card>
</CardGroup>

## Common use cases

<Tabs>
  <Tab title="Removing preambles">
    LLMs often add filler before answering or executing actions.

    **Example unwanted output:**
    "Let me check that for you. Please hold…"

    **Suggested control:**

    * **ID:** `flow_redundancy_cutoff`
    * **Regex:** `\b(let me check|please hold|one moment while I)\b`
    * **Say phrases:** TRUE

    **Result:** The agent proceeds directly to the answer or action.
  </Tab>

  <Tab title="Preventing greeting loops">
    Sometimes the agent restarts the conversation mid-call.

    **Example unwanted output:**
    "Thank you for calling Hopper…"

    **Suggested control:**

    * **ID:** `greet_loop`
    * **Regex:** `(thank you for calling|you have reached|hi thanks for calling)`
    * **Say phrases:** TRUE

    You can optionally attach a function that routes to a recovery flow instead of restarting.
  </Tab>

  <Tab title="Blocking dead ends">
    Agents sometimes respond with unhelpful refusals instead of routing.

    **Example unwanted output:**
    "I don't have information about that."

    **Suggested control:**

    * **ID:** `default_no_info`
    * **Regex:** `I don't have (any|specific)? information`
    * **Say phrases:** TRUE
    * **Function:** `prompt_OOS_Check`

    **Result:** Instead of dead-ending, the agent routes to out-of-scope handling.
  </Tab>

  <Tab title="Brand tone enforcement">
    Prevent phrasing that violates brand voice.

    **Example unwanted output:**
    "Unfortunately, I'm unable to…"

    **Suggested control:**

    * **ID:** `tone_softener`
    * **Regex:** `\b(unfortunately|sadly|regret to inform)\b`
    * **Say phrases:** TRUE

    **Result:** Forces the agent to rephrase without negative framing.
  </Tab>
</Tabs>

## Logging-only controls

Not all controls need to halt output. You can use Response Controls purely for **monitoring**.

<AccordionGroup>
  <Accordion title="Example: tracking hedging language" icon="chart-mixed">
    * **ID:** `hedge_language_monitor`
    * **Regex:** `\b(might|maybe|possibly)\b`
    * **Say phrases:** FALSE

    Tracks hedging frequency without disrupting users.
  </Accordion>
</AccordionGroup>

## Testing and verification

After adding or updating a Response Control:

<Steps>
  <Step title="Test in Chat">
    * Confirm the phrase no longer appears
    * Ensure the response still completes cleanly
  </Step>

  <Step title="Test in Call">
    * Listen for unnatural truncation
    * Ensure turn-taking still feels natural
  </Step>

  <Step title="Review Conversation logs">
    * Check whether the rule is triggering too often
    * Look for false positives
  </Step>
</Steps>

## Common mistakes

<Warning>
  **Avoid these pitfalls:**

  * Using overly broad regex patterns
  * Blocking phrases that appear in legitimate answers
  * Using Response Controls to fix retrieval problems
  * Forgetting to test in voice after chat validation
</Warning>

## Check your understanding

<Quiz
  questions={[
{
q: "What do Response Controls regulate?",
options: [
  "What the user is allowed to say",
  "How ASR processes audio input",
  "The agent's own generated output",
  "Which FAQs can be triggered",
],
correct: 2,
explanation: "Response Controls run after the LLM generates a response, before it's spoken or sent. They regulate the agent's own output – not what the user says or how speech is transcribed.",
}
]}
/>

## Pronunciations (separate feature)

<CardGroup cols={2}>
  <Card title="Response Controls" icon="shield-halved">
    Affect *what* is said
  </Card>

  <Card title="Pronunciations" icon="volume">
    Affect *how* it is spoken
  </Card>
</CardGroup>

Use **Pronunciations** when:

* Names are mispronounced
* Numbers need pacing
* Domain terms require IPA or SSML guidance

<Note>
  Do not use Response Controls to solve pronunciation issues.
</Note>

Response Controls are a surgical tool – the final layer of polish, not the first fix.

## Try it yourself

<Steps>
  <Step title="Challenge: Write a response control">
    You notice the agent frequently says "Let me look into that for you" before answering – which creates an awkward pause in voice calls.

    Write the full Response Control configuration:

    1. ID
    2. Description
    3. Regex
    4. Say phrases setting

    <Accordion title="Hint">
      Write the regex to match the specific phrase you observed, not a broad pattern. Start as narrow as possible – you can always widen it if needed.
    </Accordion>

    <Accordion title="Example solution">
      * **ID:** `filler_preamble_cutoff`
      * **Description:** Prevents the agent from saying "Let me look into that for you" before responding – this creates latency and sounds unnatural in voice.
      * **Regex:** `\blet me (look into|check) that for you\b`
      * **Say phrases:** TRUE
    </Accordion>
  </Step>
</Steps>

## Check your understanding

<Quiz
  questions={[
{
q: "What's the difference between Halt and Log in Response Controls?",
options: [
  "Halt monitors the phrase; Log blocks it",
  "Both do the same thing but at different latency costs",
  "Halt blocks the response; Log lets it through while tracking it",
  "Halt applies globally; Log applies per conversation",
],
correct: 2,
explanation: "If a phrase should never appear, Halt it – the response is blocked before reaching the user. Logging lets the phrase through while recording it, which is useful for monitoring but not for enforcement.",
}
]}
/>

<CardGroup cols={2}>
  <Card title="← Previous: Return values" icon="arrow-left" href="/learn/guides/advanced/tool-return-values">
    Lesson 3 of 8
  </Card>

  <Card title="Next: Audio management →" icon="arrow-right" href="/learn/guides/advanced/audio-management">
    Lesson 5 of 8
  </Card>
</CardGroup>

<ProgressTracker lessonKey="l2-4-response-control" lessonNum={4} totalLessons={8} level="Level 2" />
