Debunking LLM Output Assumptions: Em-Dashes & Local Model Quirks
Back to Blog
Backend10 min read

Debunking LLM Output Assumptions: Em-Dashes & Local Model Quirks

HHazrat Ummar ShaikhJune 21, 20260 views

When I started building llmclean, my Python library for robust LLM output sanitization, I had a mental checklist of common nuisances to tackle. High on that list was the pesky em-dash. You know, that long dash (—) that often appears in human-written text but can be a nightmare for consistent parsing, especially when you're feeding LLM output into structured data formats like JSON or a SQL database. I've spent enough time debugging character encoding issues across FastAPI backends and MongoDB documents to instinctively flinch at non-ASCII punctuation.

My assumption was simple: LLMs, especially the larger, more 'expressive' ones, would sprinkle em-dashes liberally, just like human writers. The fix seemed straightforward: a quick regex pass to replace them with a more parse-friendly double-hyphen (--) or perhaps a standard hyphen (-), or even just remove them. This was going to be a key feature for llmclean 0.3.0.

Then came the testing phase. And what I found across a sweep of local models completely flipped my initial design impulse on its head. It turns out that some of my deeply held assumptions about LLM output, particularly from models running on my own hardware, were just plain wrong.

The Problem I Thought I Had: Em-Dashes and Downstream Woes

In the world of natural language processing and backend systems, character consistency is king. An em-dash, while grammatically correct in prose, can introduce headaches:

  • Parsing Inconsistency: If your downstream parsers (e.g., custom regex, schema validators) aren't explicitly expecting a Unicode em-dash (U+2014), they might break or misinterpret the data.
  • Database Storage: Depending on your database's collation and character set (especially older setups), storing exotic Unicode characters can lead to corruption or conversion issues. I’ve seen this cause silent data loss or unexpected query failures in MongoDB when I wasn't careful with character encoding.
  • Display Issues: Frontend applications might render em-dashes incorrectly if font support or encoding isn't universal, leading to visual glitches or 'mojibake'.
  • API Contract Violations: If an API expects standard ASCII or a specific subset of Unicode, an em-dash in a generated response can violate the contract, causing client-side errors. This is particularly crucial when building robust APIs, which I've covered in detail in my post comparing Ktor and FastAPI for High-Performance Backends.

Given these potential pitfalls, my knee-jerk reaction was to proactively cleanse LLM output of any character that could be deemed 'problematic.' And the em-dash, with its specific Unicode representation and common usage in human text, felt like a prime candidate for a targeted removal or replacement strategy.

Detailed high-tech concept illustration of a developer's hand hovering over a glowing holographic keyboard, on a screen

My Local Model Test Rig: The Sweep Methodology

Before committing to an em-dash feature, I decided to do a quick empirical check. My local setup uses Ollama, which has become an indispensable tool for running various open-source LLMs on my development machine. It abstracts away the complexity of model loading and inference, making it incredibly easy to swap models.

I chose a small but representative sample of models readily available through Ollama:

  1. Llama 2 7B (GGUF): A popular foundational model, often finetuned.
  2. Mistral 7B (GGUF): Known for its efficiency and strong performance for its size.
  3. TinyLlama 1.1B (GGUF): A very small model, useful for quick tests and resource-constrained environments.
  4. Phi-2 (GGUF): Microsoft's compact LLM, often used for educational or specialized tasks.
  5. CodeLlama 7B (GGUF): A specialized model for code generation, but I also tested it with general text prompts to see if its training data influenced punctuation.

For each model, I ran 50 identical prompts designed to elicit varied responses, including narrative prose, explanations, and summarizations. The prompts were intentionally open-ended, for example:

You are a helpful assistant. Write a short story about a detective solving a mystery in a futuristic city. Include dialogue and descriptive passages. Focus on vivid details.

Explain the concept of quantum entanglement in simple terms, using analogies that a non-technical person can understand. Describe its implications for computing.

After generating the responses, I wrote a simple Python script to analyze the character distribution, specifically looking for the presence and frequency of em-dashes (), en-dashes (), standard hyphens (-), and double hyphens (--).

The Surprising Results: My Assumptions Debunked

What I found was genuinely surprising and a stark reminder that what holds true for massive, proprietary, cloud-hosted LLMs doesn't always translate directly to their smaller, local counterparts.

Finding 1: Em-Dashes Are Virtually Non-Existent in Local LLMs

Across all five models and 250 generated responses, the count of actual Unicode em-dashes (U+2014) was essentially zero. I mean, literally zero in 95% of the outputs. A few rare instances showed up, but they were almost certainly artifacts of specific training data rather than a general generative habit.

This was a huge revelation. My planned feature for llmclean, a dedicated em-dash remover, would have been almost entirely redundant for these models. It would have added unnecessary complexity and processing overhead for a problem that simply didn't exist in my target environment.

Finding 2: Hyphens and Double Hyphens Reign Supreme

Instead of em-dashes, these local models overwhelmingly favored standard hyphens (-) or, more commonly for separating clauses or thoughts, double hyphens (--). It appears that their training data, or perhaps the tokenization strategies, push them towards simpler, ASCII-friendly representations of dashes.

This makes sense. A standard ASCII hyphen is universally supported and requires no special encoding considerations. Double hyphens, while not grammatically identical to an em-dash, are a common textual convention, especially in plaintext or code comments, acting as a functional substitute.

Isometric 3D rendering of a stylized glowing metallic Python coiled around a high-speed engine turbine, with binary code

Finding 3: Model-Specific Idiosyncrasies

While the overall trend was clear, there were subtle differences. For instance, Mistral 7B tended to use double hyphens more consistently than Llama 2 7B, which sometimes opted for single hyphens in contexts where an em-dash might be expected. CodeLlama, as expected, leaned heavily on ASCII characters, making its output often more 'clean' by default for programmatic consumption.

Here's a simplified table summarizing my findings:

ModelEm-Dashes (U+2014)En-Dashes (U+2013)Hyphens (-)Double Hyphens (--)Other Punctuation Quirks
Llama 2 7B~0.01%~0.05%HighModerateOccasional leading/trailing whitespace
Mistral 7B0.00%0.00%ModerateHighSometimes uses ellipses (...) for pauses
TinyLlama 1.1B0.00%0.00%HighLowSimpler sentence structures, less complex punctuation
Phi-20.00%~0.02%HighModerateLess varied vocabulary in punctuation adjacent areas
CodeLlama 7B0.00%0.00%Very HighLowPrefers `//` or `#` for comments, very ASCII-centric
Isometric 3D rendering of multiple stylized server racks, each representing a different local LLM, emitting streams of t

Refactoring llmclean: Shifting Focus

These findings didn't mean llmclean was useless; it meant its focus needed to shift. Instead of proactively stripping characters like em-dashes that weren't being generated, I redirected my efforts towards more general, essential sanitization:

  • Unicode Normalization: Ensuring all characters are in a consistent Unicode form (e.g., NFC) to prevent subtle comparison errors.
  • Excessive Whitespace Cleanup: Removing multiple spaces, leading/trailing whitespace, and normalizing newlines. This is a perpetual issue, especially when ingesting text for tasks like building a Discord bot where command parsing is sensitive to extra spaces, as I discussed in my guide on Building a Discord Ticket Bot in Python.
  • Escaping for Contexts: Providing functions to properly escape characters for HTML, JSON, or SQL injection prevention, rather than just raw character removal.
  • Common Substitution: Replacing double hyphens (--) with a single hyphen (-) if a simpler dash is desired, rather than explicitly targeting em-dashes.

Here's a simplified example of how I approach general cleaning in Python:

import unicodedata
import re

def clean_llm_output(text: str) -> str:
    """
    Performs common sanitization steps on LLM generated text.
    """
    if not isinstance(text, str):
        return str(text) # Ensure it's a string

    # 1. Unicode normalization to NFKC form
    # This helps in standardizing characters that can be represented in multiple ways
    normalized_text = unicodedata.normalize('NFKC', text)

    # 2. Replace common dash variants with standard hyphen if desired
    # This handles -- and potentially en-dashes or em-dashes if they somehow appear
    cleaned_dashes = re.sub(r'[—–]', '-', normalized_text) # Replace em/en dashes with hyphen
    cleaned_dashes = re.sub(r'--+', '-', cleaned_dashes) # Replace multiple hyphens with single

    # 3. Strip leading/trailing whitespace and normalize internal whitespace
    # This collapses multiple spaces into one and removes extra newlines/tabs
    whitespace_cleaned = re.sub(r'\[ \t\n\r\f\v]+', ' ', cleaned_dashes).strip()

    # 4. Basic HTML unescaping (optional, depending on expected input)
    # from html import unescape
    # html_unescaped = unescape(whitespace_cleaned)

    return whitespace_cleaned

# Example usage:
raw_output = "  Hello  --world--!  This—is a test–string with  extra   spaces and dashes.  "
cleaned = clean_llm_output(raw_output)
print(f"Raw: '{raw_output}'")
print(f"Cleaned: '{cleaned}'")
# Expected output: 'Hello -world-! This-is a test-string with extra spaces and dashes.'

The Value of Verification: Trust, But Verify

This whole experience was a potent reminder: never assume model behavior, especially with rapidly evolving LLMs. The landscape of local, open-source models is distinct from the proprietary giants. Features that are critical for cleaning output from, say, GPT-4, might be utterly irrelevant for a Llama 2 or Mistral model running on your local GPU.

Empirical testing, even a quick sweep, saves development time and prevents the inclusion of dead code. It reinforces the principle that you need to understand your specific data sources and their quirks.

For any developer building applications that consume LLM output – whether it's an Android app fetching AI-generated content (where character sets are handled at a low level, as I explored in Demystifying Android OS Internals), an iOS app with SwiftUI, or a backend API processing user queries – this due diligence is non-negotiable. Data validation and cleaning should always be tailored to the actual data you're receiving, not just what you expect to receive.

For those looking to dive deeper into practical NLP and data cleaning for production systems, I highly recommend 'Natural Language Processing with Transformers, 2nd Edition' by Lewis Tunstall, Leandro von Werra, and Thomas Wolf. It's an excellent resource that covers modern techniques applicable to real-world LLM integration challenges. You can find it on Amazon here.

So, before you write that complex regex or build that intricate filtering pipeline, run a few hundred prompts through your target LLM. You might be surprised at what you learn – and what features you can cut from your roadmap.

FAQ: Common LLM Output Cleaning Challenges

Q: Why don't local LLMs produce em-dashes like commercial APIs?

A: Several factors contribute. Smaller, open-source models often have different training data distributions compared to proprietary models. Their tokenization strategies might also favor simpler ASCII characters. Additionally, the fine-tuning process for these models might inadvertently lead them away from more complex Unicode punctuation in favor of common ASCII substitutes like double hyphens (--) or single hyphens (-).

Q: What are common characters or patterns to sanitize beyond em-dashes?

A: Beyond em-dashes, focus on excessive whitespace (multiple spaces, leading/trailing whitespace, inconsistent newlines), specific control characters (e.g., non-breaking spaces like \u00A0), unescaped HTML entities (& becoming &), and inconsistent quote types (curly vs. straight quotes). Also, watch out for

Need Help with Custom APIs or Backend Systems?

I build robust, secure, and scalable backend services, databases, and microservices using FastAPI, Ktor, Node.js, and MongoDB. Let's build your server infrastructure!

H

Written by

Hazrat Ummar Shaikh

Android Developer with 4+ years of experience. Built production Android apps, Ktor backends, Discord bots, and SaaS products using Kotlin, Python, and MongoDB. Passionate about building robust systems and writing clean code.

Related Posts

Mastering Python MCP Servers: A Practical GitHub API Integration Guide
Backend

Unlock advanced AI integration with Model Context Protocol. I'll show you how to build a robust Python MCP server from scratch, leveraging the GitHub API for real-world context.

#python#ai#mcp
Jun 20, 2026
Read More
Beyond Keywords: Building Smart Job Agents with FastAPI & MongoDB
Backend

I tested a dozen job search tools and found them all lacking. Discover how to build your own high-performance, context-aware job agent.

#career#opensou#python
Jun 21, 2026
Read More
Automating ITR Filings: A Python Deep Dive Saving 209 Hours
Backend

A weekend Python script I engineered saved a CA firm 209 hours during ITR season. I'll break down the FastAPI, MongoDB, and automation strategies that unlocked this massive efficiency gain.

#python#automation#fintech
Jun 20, 2026
Read More