Thomas Piacentini
← Atelier

Fiche

Chatbots and Dialogue State Tracking

Texte original · anglais

Route turns, run the agent loop, track slots across turns and budget the context window. Python from the lessons.

Hybrid routing

destructiveelsematchNonetool_callUser inputclientDestructive checkdanger wordsprocessStructured flowconfirm firstprocessFAQ retrievalthreshold 0.6storeLLM agent loopRAG and tool callsmodelToolscallable functionsprocessReplyclient
Nodes and connections
NodeRoleKind
User inputclient
Destructive checkdanger wordsprocess
Structured flowconfirm firstprocess
FAQ retrievalthreshold 0.6store
LLM agent loopRAG and tool callsmodel
Toolscallable functionsprocess
Replyclient
FromToConnection
User inputDestructive check
Destructive checkStructured flowdestructive
Destructive checkFAQ retrievalelse
FAQ retrievalReplymatch
FAQ retrievalLLM agent loopNone
LLM agent loopToolstool_call
Structured flowReply
Sources

The agent loop

  1. Start history holds one user message. The loop runs max_steps times at most.
  2. LLM llm(history, tools=tools) returns content or a tool_call.
  3. Check unknown tool name or non-dict arguments add an error message. The loop continues.
  4. Tool fn(**args) runs. The call and its result go into history.
  5. Stop a reply without a tool_call is the final answer.
  6. Budget after max_steps, return "I could not complete the task in the step budget."
Sources

Tasks in code

TaskCode
FAQ answer or Nonefaq_respond(user_input, threshold=0.5)
Detect a destructive requestis_destructive_action(text)
Agent with a step budgetagent_loop(user_message, tools, llm, max_steps=5)
Score a trackerjoint_goal_accuracy(predicted_states, gold_states)
Budget the windowContextBudget(max_tokens=128000, generation_reserve=4000)
Add a component with a capbudget.allocate("tools", tool_text, max_tokens=2000)
Prune tools by intentselect_tools(query, token_budget=2000)
Best docs at start and endreorder_lost_in_middle(items, scores)
Sources

Rule-based state update

SLOT_EXTRACTORS = {"cuisine": extract_cuisine, "area": extract_area,
"price": extract_price, "people": extract_people}
def update_state(state, utterance):
new_state = dict(state)never reset untouched slots
for slot, extractor in SLOT_EXTRACTORS.items():
value = extractor(utterance)
if value is not None:
new_state[slot] = valuea correction overwrites
continue
if is_negation(utterance, slot):
new_state[slot] = None"never mind the cuisine"
return new_state
Sources

LLM tracker with a schema

class RestaurantState(BaseModel):
cuisine: Optional[Literal["italian", "chinese", "indian", "thai", "any"]] = None
area: Optional[Literal["north", "south", "east", "west", "center"]] = None
price: Optional[Literal["cheap", "moderate", "expensive"]] = None
people: Optional[int] = None
day: Optional[str] = Nonefree-form slot
def llm_dst(history, llm):
prompt = f"""...{render(history)}...Output only the JSON state."""
return llm(prompt, response_model=RestaurantState)whole state each turn
Sources

Assemble context per query

  1. System prompt goes first. Cap max_tokens=1000.
  2. Tools classify_intent then select_tools. Cap 2000.
  3. Retrieval score_relevance drops docs below the threshold. Cap 3000.
  4. Reorder reorder_lost_in_middle puts the best docs at the start and the end.
  5. History summaries plus recent turns. Cap 5000.
  6. Query goes last. Cap 500. The rest is the generation reserve.
Sources

Paradigms and DST terms

Rule-based
patterns, intents and slot filling. Fails outside its scope.
Retrieval-based
nearest stored response. No generation, so no hallucination.
Neural seq2seq
fluent, but generic replies and factual drift.
LLM agent
plan, call tool, observe result, decide next step.
Slot
a named parameter the backend needs, such as cuisine or date.
Classification DST
yes or no for each slot and candidate value. Closed vocabulary.
Generation DST
generate slot values as text. Open vocabulary. The modern default.
JGA
fraction of turns where every slot is correct. All or nothing.
Sources

Defaults and numbers

SettingDefaultNote
max_tokens128000Window size of ContextBudget
generation_reserve4000Reserve 2,000-4,000 for the answer
max_history_tokens5000Above it, summarize the 2 oldest turns
token_budget2000Tool definitions for one query
Relevance threshold0.05The lesson text uses 0.1
Token estimate1.3Tokens for each whitespace word
max_steps5Agent step budget
FAQ threshold0.5Hybrid route uses 0.6
Sources

Troubleshooting

SymptomCauseFix
Agent claims an action it did not doConfident fabricationTrust only a successful tool return
Input overrides the system promptPrompt injectionUntrusted input, PVE, confirm destructive
Same tool called again and againInfinite loopStep budget, dedupe tool calls
Earliest turns lostContext window fullSummarize or retrieve old turns
Fact in the middle ignoredLost in the middleKey facts first or last
Correction clears other slotsReset of untouched slotsOverwrite only the corrected slot
"Italian" and "italian" differCaseNormalize everywhere
Old training data breaksNew slots added laterVersion the schema
Sources

Abbreviations

DST
Dialogue State Tracking. Keeps the slot-value dict across turns.
JGA
Joint Goal Accuracy. MultiWOZ 2.4 top systems reach 80-83%.
MultiWOZ
Multi-domain WOZ dataset. The standard DST benchmark.
LDST
LLaMA plus LoRA, instruction-tuned with domain-slot prompts.
TripPy
copy-based DST with BERT encoding. Pre-LLM standard.
PVE
Plan-Verify-Execute. Check each action against the plan first.
RAG
retrieve relevant docs, then ground the reply in them.
Lost-in-the-middle
85-90% accuracy at the ends, 60-70% in the middle.
Sources

Questions

Why route instead of one model?
No single architecture handles every request well.
Why rules for payments?
Destructive actions need deterministic, confirmed flows.
Incremental update or regenerate?
Regenerate the whole state from history. It handles corrections.
What does regeneration cost?
O(n^2) total tokens. Cap or summarize old turns.
Is a bigger window enough?
No. A curated 10K context often beats a dumped 100K.
Sources

Tips

  • Below the FAQ threshold, return None and escalate.
  • Log every agent step. Measure off-task rate.
  • Mix free-form slots (names, times) with closed slots (cuisine, area).
  • Always pass the full history so earlier slots stay set.
  • Tool pruning cuts tool tokens by 60-80%.
  • Two or three good few-shot examples often beat long instructions.
Sources
Original source
---
id: dialogue-systems
title: Chatbots and Dialogue State Tracking
code: CS
subtitle: Route turns, run the agent loop, track slots across turns and budget the context window. Python from the lessons.
hue: 170
sources: phases/05-nlp-foundations-to-advanced/17-chatbots-rule-to-neural, phases/05-nlp-foundations-to-advanced/29-dialogue-state-tracking, phases/11-llm-engineering/05-context-engineering
source_hash: e4e39898a4e411b5
---

## Hybrid routing
type: architecture
from: phases/05-nlp-foundations-to-advanced/17-chatbots-rule-to-neural/docs/en.md

```arch
node | in | 1,1 | User input | | client
node | rule | 2,1 | Destructive check | danger words | process
node | flow | 3,1 | Structured flow | confirm first | process
node | faq | 2,2 | FAQ retrieval | threshold 0.6 | store
node | agent | 3,2 | LLM agent loop | RAG and tool calls | model
node | tools | 4,2 | Tools | callable functions | process
node | out | 4,1 | Reply | | client
edge | in | rule |
edge | rule | flow | destructive
edge | rule | faq | else
edge | faq | out | match
edge | faq | agent | None
edge | agent | tools | tool_call
edge | flow | out |
```

## The agent loop
type: flow
from: phases/05-nlp-foundations-to-advanced/17-chatbots-rule-to-neural/docs/en.md

1. **Start**: `history` holds one user message. The loop runs `max_steps` times at most.
2. **LLM**: `llm(history, tools=tools)` returns content or a `tool_call`.
3. **Check**: unknown tool name or non-dict arguments add an error message. The loop continues.
4. **Tool**: `fn(**args)` runs. The call and its result go into `history`.
5. **Stop**: a reply without a `tool_call` is the final answer.
6. **Budget**: after `max_steps`, return "I could not complete the task in the step budget."

## Tasks in code
type: task-table
span: 2
from: phases/05-nlp-foundations-to-advanced/17-chatbots-rule-to-neural/docs/en.md, phases/05-nlp-foundations-to-advanced/29-dialogue-state-tracking/docs/en.md, phases/11-llm-engineering/05-context-engineering/code/main.py

| Task | Code |
|---|---|
| FAQ answer or `None` | `faq_respond(user_input, threshold=0.5)` |
| Detect a destructive request | `is_destructive_action(text)` |
| Agent with a step budget | `agent_loop(user_message, tools, llm, max_steps=5)` |
| Score a tracker | `joint_goal_accuracy(predicted_states, gold_states)` |
| Budget the window | `ContextBudget(max_tokens=128000, generation_reserve=4000)` |
| Add a component with a cap | `budget.allocate("tools", tool_text, max_tokens=2000)` |
| Prune tools by intent | `select_tools(query, token_budget=2000)` |
| Best docs at start and end | `reorder_lost_in_middle(items, scores)` |

## Rule-based state update
type: annotated-code
lang: python
from: phases/05-nlp-foundations-to-advanced/29-dialogue-state-tracking/code/main.py

```python
SLOT_EXTRACTORS = {"cuisine": extract_cuisine, "area": extract_area,
                   "price": extract_price, "people": extract_people}

def update_state(state, utterance):
    new_state = dict(state)                    # never reset untouched slots
    for slot, extractor in SLOT_EXTRACTORS.items():
        value = extractor(utterance)
        if value is not None:
            new_state[slot] = value            # a correction overwrites
            continue
        if is_negation(utterance, slot):
            new_state[slot] = None             # "never mind the cuisine"
    return new_state
```

## LLM tracker with a schema
type: annotated-code
lang: python
from: phases/05-nlp-foundations-to-advanced/29-dialogue-state-tracking/docs/en.md

```python
class RestaurantState(BaseModel):
    cuisine: Optional[Literal["italian", "chinese", "indian", "thai", "any"]] = None
    area: Optional[Literal["north", "south", "east", "west", "center"]] = None
    price: Optional[Literal["cheap", "moderate", "expensive"]] = None
    people: Optional[int] = None
    day: Optional[str] = None                  # free-form slot

def llm_dst(history, llm):
    prompt = f"""...{render(history)}...Output only the JSON state."""
    return llm(prompt, response_model=RestaurantState)  # whole state each turn
```

## Assemble context per query
type: flow
from: phases/11-llm-engineering/05-context-engineering/docs/en.md, phases/11-llm-engineering/05-context-engineering/code/main.py

1. **System prompt**: goes first. Cap `max_tokens=1000`.
2. **Tools**: `classify_intent` then `select_tools`. Cap 2000.
3. **Retrieval**: `score_relevance` drops docs below the threshold. Cap 3000.
4. **Reorder**: `reorder_lost_in_middle` puts the best docs at the start and the end.
5. **History**: summaries plus recent turns. Cap 5000.
6. **Query**: goes last. Cap 500. The rest is the generation reserve.

## Paradigms and DST terms
type: concepts
from: phases/05-nlp-foundations-to-advanced/17-chatbots-rule-to-neural/docs/en.md, phases/05-nlp-foundations-to-advanced/29-dialogue-state-tracking/docs/en.md

- **Rule-based**: patterns, intents and slot filling. Fails outside its scope.
- **Retrieval-based**: nearest stored response. No generation, so no hallucination.
- **Neural seq2seq**: fluent, but generic replies and factual drift.
- **LLM agent**: plan, call tool, observe result, decide next step.
- **Slot**: a named parameter the backend needs, such as cuisine or date.
- **Classification DST**: yes or no for each slot and candidate value. Closed vocabulary.
- **Generation DST**: generate slot values as text. Open vocabulary. The modern default.
- **JGA**: fraction of turns where every slot is correct. All or nothing.

## Defaults and numbers
type: defaults
from: phases/11-llm-engineering/05-context-engineering/code/main.py, phases/11-llm-engineering/05-context-engineering/docs/en.md, phases/05-nlp-foundations-to-advanced/17-chatbots-rule-to-neural/docs/en.md

| Setting | Default | Note |
|---|---|---|
| `max_tokens` | 128000 | Window size of `ContextBudget` |
| `generation_reserve` | 4000 | Reserve 2,000-4,000 for the answer |
| `max_history_tokens` | 5000 | Above it, summarize the 2 oldest turns |
| `token_budget` | 2000 | Tool definitions for one query |
| Relevance threshold | 0.05 | The lesson text uses 0.1 |
| Token estimate | 1.3 | Tokens for each whitespace word |
| `max_steps` | 5 | Agent step budget |
| FAQ `threshold` | 0.5 | Hybrid route uses 0.6 |

## Troubleshooting
type: troubleshooting
from: phases/05-nlp-foundations-to-advanced/17-chatbots-rule-to-neural/docs/en.md, phases/05-nlp-foundations-to-advanced/29-dialogue-state-tracking/docs/en.md, phases/11-llm-engineering/05-context-engineering/docs/en.md

| Symptom | Cause | Fix |
|---|---|---|
| Agent claims an action it did not do | Confident fabrication | Trust only a successful tool return |
| Input overrides the system prompt | Prompt injection | Untrusted input, PVE, confirm destructive |
| Same tool called again and again | Infinite loop | Step budget, dedupe tool calls |
| Earliest turns lost | Context window full | Summarize or retrieve old turns |
| Fact in the middle ignored | Lost in the middle | Key facts first or last |
| Correction clears other slots | Reset of untouched slots | Overwrite only the corrected slot |
| "Italian" and "italian" differ | Case | Normalize everywhere |
| Old training data breaks | New slots added later | Version the schema |

## Abbreviations
type: glossary
from: phases/05-nlp-foundations-to-advanced/17-chatbots-rule-to-neural/docs/en.md, phases/05-nlp-foundations-to-advanced/29-dialogue-state-tracking/docs/en.md, phases/11-llm-engineering/05-context-engineering/docs/en.md

- **DST**: Dialogue State Tracking. Keeps the slot-value dict across turns.
- **JGA**: Joint Goal Accuracy. MultiWOZ 2.4 top systems reach 80-83%.
- **MultiWOZ**: Multi-domain WOZ dataset. The standard DST benchmark.
- **LDST**: LLaMA plus LoRA, instruction-tuned with domain-slot prompts.
- **TripPy**: copy-based DST with BERT encoding. Pre-LLM standard.
- **PVE**: Plan-Verify-Execute. Check each action against the plan first.
- **RAG**: retrieve relevant docs, then ground the reply in them.
- **Lost-in-the-middle**: 85-90% accuracy at the ends, 60-70% in the middle.

## Questions
type: qa
from: phases/05-nlp-foundations-to-advanced/17-chatbots-rule-to-neural/docs/en.md, phases/05-nlp-foundations-to-advanced/29-dialogue-state-tracking/docs/en.md, phases/11-llm-engineering/05-context-engineering/docs/en.md

- **Why route instead of one model?** No single architecture handles every request well.
- **Why rules for payments?** Destructive actions need deterministic, confirmed flows.
- **Incremental update or regenerate?** Regenerate the whole state from history. It handles corrections.
- **What does regeneration cost?** $O(n^2)$ total tokens. Cap or summarize old turns.
- **Is a bigger window enough?** No. A curated 10K context often beats a dumped 100K.

## Tips
type: tips
from: phases/05-nlp-foundations-to-advanced/17-chatbots-rule-to-neural/docs/en.md, phases/05-nlp-foundations-to-advanced/29-dialogue-state-tracking/docs/en.md, phases/11-llm-engineering/05-context-engineering/docs/en.md

- Below the FAQ threshold, return `None` and escalate.
- Log every agent step. Measure off-task rate.
- Mix free-form slots (names, times) with closed slots (cuisine, area).
- Always pass the full history so earlier slots stay set.
- Tool pruning cuts tool tokens by 60-80%.
- Two or three good few-shot examples often beat long instructions.
Code et livrables 1
Ce que vous en retenez

Enregistré dans votre carnet sur cet appareil.

Sources et limites

Les repères de lecture sont déclaratifs. Les résultats de tests sont enregistrés séparément ; ils valent pour les cas exécutés.

AI Engineering from Scratch · 3f9715c · MIT

Télécharger ce fichier ↓