<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://hzxbzp.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://hzxbzp.github.io/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-09-03T01:09:05+00:00</updated><id>https://hzxbzp.github.io/feed.xml</id><title type="html">Zhipeng Bao</title><subtitle>AI · Robotics · Autonomous Driving — Thoughts on technology and life</subtitle><author><name>Zhipeng Bao</name></author><entry xml:lang="en"><title type="html">The Art of Talking to AI: A Practical Guide to Prompt Engineering</title><link href="https://hzxbzp.github.io/blog/2026/05/prompt-engineering-guide/" rel="alternate" type="text/html" title="The Art of Talking to AI: A Practical Guide to Prompt Engineering" /><published>2026-05-18T00:00:00+00:00</published><updated>2026-05-18T00:00:00+00:00</updated><id>https://hzxbzp.github.io/blog/2026/05/prompt-engineering-guide</id><content type="html" xml:base="https://hzxbzp.github.io/blog/2026/05/prompt-engineering-guide/"><![CDATA[<p>Same model, same question, wildly different answers. I discovered that the gap between a useless AI response and a brilliant one often has nothing to do with the model — it’s all about how you ask.</p>

<h2 id="the-story-one-assignment-six-techniques">The Story: One Assignment, Six Techniques</h2>

<p>My CS146S course (The Modern Software Developer) gave us a set of coding exercises that all shared the same structure: get Llama 3.1 (8B) to solve a task correctly. The twist? Each exercise required a <em>different prompting technique</em> — and the difference in results was shocking.</p>

<p>Same model. Same hardware. Completely different capabilities, just by changing the prompt.</p>

<p>Here are the six techniques I implemented, ordered from simplest to most powerful.</p>

<p><img src="/assets/images/blog/prompt-techniques-overview.svg" alt="Prompt techniques from basic to advanced" /></p>

<h2 id="1-few-shot-prompting-learn-by-example">1. Few-Shot Prompting: “Learn by Example”</h2>

<p><strong>The idea:</strong> Instead of explaining the rules, show the model examples of correct input-output pairs and let it figure out the pattern.</p>

<p>In my assignment, I needed Llama to reverse the word “httpstatus.” Instead of explaining reversal, I gave it examples:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">system_prompt</span> <span class="o">=</span> <span class="s">"""You reverse the letters of a word. 
Output ONLY the reversed word.

Word: httpstatus
Reversed: sutatsptth

Word: hello
Reversed: olleh

Word: httpstatus
Reversed: sutatsptth"""</span>
</code></pre></div></div>

<p><strong>Why it works:</strong> LLMs are pattern-completion machines. When they see a consistent pattern of input → output, they extrapolate. Few-shot prompting exploits this by giving the model a “template” to follow.</p>

<p><strong>Watch out for:</strong></p>
<ul>
  <li><strong>Example quality matters more than quantity.</strong> One wrong example can derail the model. Three clean, diverse examples usually beat ten repetitive ones.</li>
  <li><strong>Order affects results.</strong> Put simpler examples first, harder ones last. The model pays more attention to recent context.</li>
  <li><strong>It doesn’t teach understanding.</strong> The model mimics the pattern without truly “getting” it — which is why it still fails on edge cases like unusual tokenization (see <a href="/blog/2026/05/why-llms-cant-reverse-words/">my first blog post</a>).</li>
</ul>

<h2 id="2-chain-of-thought-think-step-by-step">2. Chain-of-Thought: “Think Step by Step”</h2>

<p><strong>The idea:</strong> Ask the model to show its reasoning process before giving the final answer.</p>

<p>I used this to solve 3^{12345} mod 100:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">system_prompt</span> <span class="o">=</span> <span class="s">"""You solve user's problem. Think step by step. 
Output the reasoning trace and the final answer."""</span>
</code></pre></div></div>

<p><strong>Why it works:</strong> When you force the model to generate intermediate steps, those steps become additional context that guides the final answer. It’s like giving the model a “scratch pad” — the act of writing out the reasoning helps it stay on track.</p>

<p><strong>Watch out for:</strong></p>
<ul>
  <li><strong>The reasoning might be fake.</strong> As I discovered (and <a href="/blog/2026/05/llm-right-answer-wrong-reasoning/">wrote about</a>), the model can generate plausible-looking steps that don’t actually support the conclusion. It may know the answer and build the reasoning backwards.</li>
  <li><strong>Longer outputs cost more.</strong> CoT increases token usage significantly. For simple tasks, it’s overkill.</li>
  <li><strong>Garbage in, garbage out.</strong> If the first step goes wrong, every subsequent step compounds the error.</li>
</ul>

<h2 id="3-self-consistency-ask-three-times-trust-the-majority">3. Self-Consistency: “Ask Three Times, Trust the Majority”</h2>

<p><strong>The idea:</strong> Have the model solve the same problem multiple times using different approaches, then pick the most common answer.</p>

<p>My implementation was the most elaborate prompt of the set:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">system_prompt</span> <span class="o">=</span> <span class="s">"""You will solve every problem multiple times 
using different reasoning approaches.

1. Reasoning 1: Solve step by step, directly.
   Answer: &lt;number&gt;

2. Reasoning 2: Solve again from scratch, 
   using a different framing (e.g. work backwards).
   Answer: &lt;number&gt;

3. Reasoning 3: Solve by checking concrete 
   positions or values.
   Answer: &lt;number&gt;

4. Compare the three answers. Pick the majority vote.
   Answer: &lt;number&gt;"""</span>
</code></pre></div></div>

<p>The code then ran this 5 times and took the majority answer across all runs.</p>

<p><strong>Why it works:</strong> Different reasoning paths hit different failure modes. If two out of three approaches agree, the shared answer is more likely to be correct. It’s the same logic behind “ask three doctors”: one might be wrong, but if two agree, trust them.</p>

<p><strong>Watch out for:</strong></p>
<ul>
  <li><strong>Cost multiplies.</strong> You’re using 3x the tokens per call, and running multiple calls. Budget accordingly.</li>
  <li><strong>Diversity matters.</strong> If all three “different” approaches are secretly the same method rephrased, you get no benefit. Be specific about <em>how</em> each approach should differ.</li>
  <li><strong>Not all tasks benefit.</strong> For creative writing or subjective tasks, there’s no “correct” answer to vote on.</li>
</ul>

<h2 id="4-rag-heres-what-you-need-to-know">4. RAG: “Here’s What You Need to Know”</h2>

<p><strong>The idea:</strong> Give the model external documents as context so it doesn’t have to rely on (potentially outdated or incorrect) training data.</p>

<p>My assignment required writing a function that calls a documented API. The key was feeding the API docs directly into the prompt:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">user_prompt</span> <span class="o">=</span> <span class="sa">f</span><span class="s">"""Context (use ONLY this information):
</span><span class="si">{</span><span class="n">api_docs</span><span class="si">}</span><span class="s">

Task: Write a Python function fetch_user_name(user_id, api_key) 
that calls the documented API.

Requirements:
- Use the documented Base URL and endpoint.
- Send the documented authentication header.
- Return only the user's name string."""</span>
</code></pre></div></div>

<p><strong>Why it works:</strong> LLMs hallucinate when they don’t have information. RAG solves this by providing the exact information the model needs, right in the prompt. No guessing, no hallucinating endpoints or parameters.</p>

<p><strong>Watch out for:</strong></p>
<ul>
  <li><strong>Context window limits.</strong> You can’t dump an entire codebase into a prompt. Choose the most relevant documents carefully.</li>
  <li><strong>“Use ONLY this information” is important.</strong> Without it, the model might mix context with training data and invent fields that don’t exist.</li>
  <li><strong>Retrieval quality is everything.</strong> RAG is only as good as the documents you feed it. Bad retrieval → bad answers, no matter how good the model is.</li>
</ul>

<h2 id="5-reflexion-learn-from-your-mistakes">5. Reflexion: “Learn From Your Mistakes”</h2>

<p><strong>The idea:</strong> Let the model generate an answer, test it, show it what went wrong, and ask it to fix the code.</p>

<p>My assignment implemented a two-pass loop: generate → test → show failures → regenerate. A key detail: <strong>the two passes use different system prompts.</strong> The first pass gets a simple generation prompt; the reflexion pass gets a specialized correction prompt with the failure context.</p>

<p><strong>Pass 1 — Initial generation</strong> (simple, no feedback):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">system_prompt</span> <span class="o">=</span> <span class="s">"""You are a coding assistant. Output ONLY a single 
fenced Python code block that defines the function 
is_valid_password(password: str) -&gt; bool. No prose or comments."""</span>
</code></pre></div></div>

<p><strong>Pass 2 — Reflexion</strong> (given the previous code + what went wrong):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">reflexion_prompt</span> <span class="o">=</span> <span class="s">"""You are a coding assistant performing 
self-correction (reflexion).

You will be given:
1. A previous implementation that failed some test cases.
2. A list of failing test cases with expected vs. actual results 
   and specific validation rules that failed.

Your job:
- Read the failure report carefully.
- Identify exactly which checks were wrong, missed, or inverted.
- Produce a corrected implementation."""</span>
</code></pre></div></div>

<p>The reflexion prompt receives the actual failing output as context — for example: <code class="language-plaintext highlighter-rouge">"Input: Password1 → expected True, got False. Failing checks: missing special"</code>. The first pass often missed edge cases. But after seeing <em>exactly</em> which tests failed and why, the second pass nailed it.</p>

<p><strong>Why it works:</strong> Reflexion mimics how humans debug: write code → run tests → read errors → fix. The model doesn’t need to get it right the first time; it just needs to get it right <em>eventually</em>, guided by concrete feedback.</p>

<p><strong>Watch out for:</strong></p>
<ul>
  <li><strong>Feedback quality is critical.</strong> “It’s wrong, try again” is useless. “Input ‘Password1’ returned True but should return False because it’s missing a special character” — that’s actionable.</li>
  <li><strong>One iteration might not be enough.</strong> Complex bugs may need multiple rounds of reflexion. But too many rounds risk the model going in circles.</li>
  <li><strong>The model can over-correct.</strong> It might fix the failing case but break a previously passing one. Always re-run all tests, not just the failing ones.</li>
</ul>

<h2 id="6-tool-calling-use-real-tools-dont-pretend">6. Tool Calling: “Use Real Tools, Don’t Pretend”</h2>

<p><strong>The idea:</strong> Instead of asking the model to compute or guess, let it call external tools (APIs, code interpreters, databases) and work with real results.</p>

<p>My assignment required the model to call a Python function that parses source files:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">system_prompt</span> <span class="o">=</span> <span class="s">"""You are a tool-calling assistant. 
Respond with ONLY a JSON object:
{"tool": "&lt;tool_name&gt;", "args": {&lt;arguments&gt;}}

Available tools:
[
  {
    "name": "output_every_func_return_type",
    "description": "Parse a Python file and return 
    function_name: return_type for each function.",
    "parameters": {
      "file_path": { "type": "string" }
    }
  }
]"""</span>
</code></pre></div></div>

<p>The model outputs a structured tool call; the code <em>actually executes</em> the tool and returns real results.</p>

<p><strong>Why it works:</strong> This is the solution to the “fake code execution” problem from my <a href="/blog/2026/05/llm-right-answer-wrong-reasoning/">second blog post</a>. Instead of the model <em>pretending</em> to run code, it <em>actually</em> runs code. Instead of <em>guessing</em> what an API returns, it <em>calls</em> the API. Real data, real results, no hallucination.</p>

<p><strong>Watch out for:</strong></p>
<ul>
  <li><strong>Prompt format must be strict.</strong> The model needs to output valid JSON that your code can parse. One extra word outside the JSON and parsing fails. Be very explicit about the output format.</li>
  <li><strong>Security matters.</strong> If the model can call arbitrary tools, it can do damage. Always validate and sandbox tool calls.</li>
  <li><strong>Not every model supports it natively.</strong> Smaller models may struggle with structured JSON output. Larger models (GPT-4, Claude) have built-in tool-calling support.</li>
</ul>

<h2 id="why-do-these-techniques-exist">Why Do These Techniques Exist?</h2>

<p>All six techniques address the same root problem: <strong>LLMs are text predictors, not reasoners.</strong> They predict the most likely next token given the context. Every prompting technique is essentially a way to <em>engineer the context</em> so that the most likely next token is also the correct one.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Technique</th>
      <th style="text-align: left">Core Strategy</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Few-Shot</td>
      <td style="text-align: left">Set up a pattern the model can continue</td>
    </tr>
    <tr>
      <td style="text-align: left">Chain-of-Thought</td>
      <td style="text-align: left">Create intermediate context that guides the answer</td>
    </tr>
    <tr>
      <td style="text-align: left">Self-Consistency</td>
      <td style="text-align: left">Reduce variance by aggregating multiple attempts</td>
    </tr>
    <tr>
      <td style="text-align: left">RAG</td>
      <td style="text-align: left">Replace guessing with grounding in real data</td>
    </tr>
    <tr>
      <td style="text-align: left">Reflexion</td>
      <td style="text-align: left">Use feedback to iteratively narrow down the correct answer</td>
    </tr>
    <tr>
      <td style="text-align: left">Tool Calling</td>
      <td style="text-align: left">Bypass generation entirely for tasks that need computation</td>
    </tr>
  </tbody>
</table>

<p>The progression tells a story: we started by hoping the model would “just get it,” and gradually moved toward giving it more structure, more data, and more tools — because hoping isn’t a strategy.</p>

<h2 id="try-it-yourself">Try It Yourself</h2>

<p>Want to practice prompt engineering? Here are two excellent resources:</p>

<p><strong><a href="https://www.promptingguide.ai/">Prompting Guide</a></strong> — A comprehensive, open-source reference covering every technique from zero-shot to advanced agent patterns. Great as a lookup reference.</p>

<p><strong><a href="https://learnprompting.org/">Learn Prompting</a></strong> — An interactive course with hands-on exercises. Free and well-structured for beginners to advanced users.</p>

<p><strong><a href="https://github.com/anthropics/prompt-eng-interactive-tutorial">Anthropic’s Interactive Tutorial</a></strong> — Jupyter notebook-based course with a built-in playground at the bottom of each lesson for live experimentation.</p>

<h2 id="key-takeaways">Key Takeaways</h2>

<ol>
  <li><strong>The prompt is the program.</strong> How you ask is often more important than which model you use.</li>
  <li><strong>Start simple, escalate as needed.</strong> Few-shot handles most tasks; reach for CoT, RAG, or tools only when simple prompts fail.</li>
  <li><strong>Every technique has a cost.</strong> More tokens, more API calls, more complexity. Match the technique to the stakes.</li>
  <li><strong>Always verify outputs.</strong> No technique eliminates hallucinations entirely — they just reduce the probability.</li>
  <li><strong>Combine techniques.</strong> The real power comes from mixing: RAG + CoT + Tool Calling is how production AI systems work.</li>
</ol>

<p style="font-size: 1.4em; font-style: italic; text-align: center; margin: 2rem 0;">Every prompting trick is just a communication skill we forgot we already had.</p>

<hr />

<h2 id="useful-resources">Useful Resources</h2>

<ul>
  <li><a href="https://www.promptingguide.ai/">Prompt Engineering Guide</a></li>
  <li><a href="https://learnprompting.org/">Learn Prompting — Free Interactive Course</a></li>
  <li><a href="https://github.com/anthropics/prompt-eng-interactive-tutorial">Anthropic Interactive Prompt Engineering Tutorial (GitHub)</a></li>
  <li><a href="https://platform.openai.com/docs/guides/prompt-engineering">OpenAI Prompt Engineering Best Practices</a></li>
  <li><a href="https://arxiv.org/abs/2203.11171">Self-Consistency Improves Chain of Thought Reasoning (arXiv)</a></li>
  <li><a href="https://arxiv.org/abs/2303.11366">Reflexion: Language Agents with Verbal Reinforcement Learning (arXiv)</a></li>
  <li><a href="https://arxiv.org/abs/2201.11903">Chain-of-Thought Prompting Elicits Reasoning in LLMs (arXiv)</a></li>
  <li><a href="https://arxiv.org/abs/2005.11401">Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks (arXiv)</a></li>
</ul>]]></content><author><name>Zhipeng Bao</name></author><category term="AI" /><category term="LLM" /><category term="prompt-engineering" /><category term="tutorial" /><category term="techniques" /><summary type="html"><![CDATA[Same model, same question, wildly different answers. I discovered that the gap between a useless AI response and a brilliant one often has nothing to do with the model — it’s all about how you ask.]]></summary></entry><entry xml:lang="zh"><title type="html">和 AI 对话的艺术：提示工程实用指南</title><link href="https://hzxbzp.github.io/zh/blog/2026/05/prompt-engineering-guide/" rel="alternate" type="text/html" title="和 AI 对话的艺术：提示工程实用指南" /><published>2026-05-18T00:00:00+00:00</published><updated>2026-05-18T00:00:00+00:00</updated><id>https://hzxbzp.github.io/zh/blog/2026/05/zh-prompt-engineering-guide</id><content type="html" xml:base="https://hzxbzp.github.io/zh/blog/2026/05/prompt-engineering-guide/"><![CDATA[<p>同一个模型，同一个问题，答案却天差地别。我发现，一个没用的 AI 回答和一个惊艳的回答之间的差距，往往和模型本身无关——关键在于你怎么问。</p>

<h2 id="故事的起点一次作业六种技巧">故事的起点：一次作业，六种技巧</h2>

<p>我的 CS146S 课程（The Modern Software Developer）布置了一组编程练习，它们有一个共同的结构：让 Llama 3.1（8B）正确完成一个任务。关键在于，每道题要求使用<em>不同的提示技巧</em>——而结果的差异令人震惊。</p>

<p>同一个模型，同一台机器，能力却完全不同，仅仅因为换了一种提问方式。</p>

<p>以下是我实现的六种技巧，按从简单到强大的顺序排列。</p>

<p><img src="/assets/images/blog/prompt-techniques-overview.svg" alt="从基础到高级的提示技巧" /></p>

<h2 id="1-少样本提示few-shot-prompting用例子来教">1. 少样本提示（Few-Shot Prompting）：”用例子来教”</h2>

<p><strong>核心思路：</strong> 不要解释规则，而是给模型展示正确的输入-输出示例，让它自己找到规律。</p>

<p>在我的作业中，我需要让 Llama 反转 “httpstatus” 这个词。与其解释什么是反转，不如直接给它看例子：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">system_prompt</span> <span class="o">=</span> <span class="s">"""You reverse the letters of a word. 
Output ONLY the reversed word.

Word: httpstatus
Reversed: sutatsptth

Word: hello
Reversed: olleh

Word: httpstatus
Reversed: sutatsptth"""</span>
</code></pre></div></div>

<p><strong>为什么有效：</strong> 大语言模型本质上是模式补全机器。当它们看到一致的输入 → 输出模式时，就会自动推断。少样本提示正是利用了这一点，给模型一个可以遵循的”模板”。</p>

<p><strong>需要注意的坑：</strong></p>
<ul>
  <li><strong>示例的质量比数量重要得多。</strong> 一个错误的示例就能把模型带偏。三个干净、多样的示例通常比十个重复的效果好。</li>
  <li><strong>顺序会影响结果。</strong> 把简单的例子放前面，难的放后面。模型对最近的上下文更敏感。</li>
  <li><strong>它并不是真的”理解”了。</strong> 模型只是在模仿模式，并没有真正”搞懂”——这也是为什么它在边界情况（比如不常见的分词方式）下依然会失败（参见<a href="/zh/blog/2026/05/why-llms-cant-reverse-words/">我的第一篇博客</a>）。</li>
</ul>

<h2 id="2-思维链chain-of-thought一步一步想">2. 思维链（Chain-of-Thought）：”一步一步想”</h2>

<p><strong>核心思路：</strong> 要求模型在给出最终答案之前，先展示推理过程。</p>

<p>我用这个技巧来计算 3^{12345} mod 100：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">system_prompt</span> <span class="o">=</span> <span class="s">"""You solve user's problem. Think step by step. 
Output the reasoning trace and the final answer."""</span>
</code></pre></div></div>

<p><strong>为什么有效：</strong> 当你强制模型生成中间步骤时，这些步骤会变成额外的上下文，引导最终答案的生成。就像给模型一张”草稿纸”——把推理过程写出来这个动作本身，就能帮助它保持在正确的轨道上。</p>

<p><strong>需要注意的坑：</strong></p>
<ul>
  <li><strong>推理过程可能是假的。</strong> 正如我发现并<a href="/zh/blog/2026/05/llm-right-answer-wrong-reasoning/">写过的</a>，模型可能生成看起来很合理的步骤，但这些步骤实际上并不支持它的结论。它可能先知道答案，再反过来编造推理过程。</li>
  <li><strong>更长的输出意味着更高的成本。</strong> 思维链会显著增加 token 消耗。对于简单任务来说，没必要。</li>
  <li><strong>垃圾进，垃圾出。</strong> 如果第一步就错了，后面每一步都会把错误放大。</li>
</ul>

<h2 id="3-自洽性self-consistency问三遍听多数的">3. 自洽性（Self-Consistency）：”问三遍，听多数的”</h2>

<p><strong>核心思路：</strong> 让模型用不同的方法多次解决同一个问题，然后选择出现次数最多的答案。</p>

<p>我的实现是整组作业中最精心设计的提示：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">system_prompt</span> <span class="o">=</span> <span class="s">"""You will solve every problem multiple times 
using different reasoning approaches.

1. Reasoning 1: Solve step by step, directly.
   Answer: &lt;number&gt;

2. Reasoning 2: Solve again from scratch, 
   using a different framing (e.g. work backwards).
   Answer: &lt;number&gt;

3. Reasoning 3: Solve by checking concrete 
   positions or values.
   Answer: &lt;number&gt;

4. Compare the three answers. Pick the majority vote.
   Answer: &lt;number&gt;"""</span>
</code></pre></div></div>

<p>代码会把这个过程跑 5 次，然后在所有运行结果中取多数票。</p>

<p><strong>为什么有效：</strong> 不同的推理路径会碰到不同的失败模式。如果三种方法中有两种得出了相同的答案，那个共同答案更可能是对的。道理就像”找三个医生看看”：一个可能误诊，但如果两个意见一致，就更值得信赖。</p>

<p><strong>需要注意的坑：</strong></p>
<ul>
  <li><strong>成本会翻倍。</strong> 每次调用消耗 3 倍的 token，而且要运行多次。要提前算好预算。</li>
  <li><strong>多样性很重要。</strong> 如果三种”不同”的方法本质上只是同一种方法的不同说法，那就没有任何好处。要明确指定每种方法<em>具体</em>应该怎么不同。</li>
  <li><strong>不是所有任务都适用。</strong> 对于创意写作或主观性任务，没有一个”正确答案”可以投票。</li>
</ul>

<h2 id="4-检索增强生成rag这是你需要知道的">4. 检索增强生成（RAG）：”这是你需要知道的”</h2>

<p><strong>核心思路：</strong> 给模型提供外部文档作为上下文，这样它就不用依赖（可能过时或错误的）训练数据。</p>

<p>我的作业要求编写一个调用文档化 API 的函数。关键是把 API 文档直接喂进提示里：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">user_prompt</span> <span class="o">=</span> <span class="sa">f</span><span class="s">"""Context (use ONLY this information):
</span><span class="si">{</span><span class="n">api_docs</span><span class="si">}</span><span class="s">

Task: Write a Python function fetch_user_name(user_id, api_key) 
that calls the documented API.

Requirements:
- Use the documented Base URL and endpoint.
- Send the documented authentication header.
- Return only the user's name string."""</span>
</code></pre></div></div>

<p><strong>为什么有效：</strong> 大语言模型在缺乏信息时会产生幻觉（Hallucination）。RAG 通过在提示中直接提供模型需要的确切信息来解决这个问题。不用猜测，不会凭空捏造端点或参数。</p>

<p><strong>需要注意的坑：</strong></p>
<ul>
  <li><strong>上下文窗口（Context Window）有限。</strong> 你不可能把整个代码库都塞进提示里。要精心挑选最相关的文档。</li>
  <li><strong>“仅使用以下信息”这句话很重要。</strong> 没有它，模型可能会把上下文和训练数据混在一起，发明出根本不存在的字段。</li>
  <li><strong>检索质量决定一切。</strong> RAG 的效果取决于你喂给它的文档。检索质量差 → 答案就差，模型再好也没用。</li>
</ul>

<h2 id="5-反思reflexion从错误中学习">5. 反思（Reflexion）：”从错误中学习”</h2>

<p><strong>核心思路：</strong> 让模型先生成一个答案，测试它，把哪里错了告诉它，然后让它修复代码。</p>

<p>我的作业实现了一个两轮循环：生成 → 测试 → 展示失败 → 重新生成。一个关键细节：<strong>两轮使用不同的系统提示。</strong> 第一轮用简单的生成提示；反思轮用专门的纠错提示，并附带失败的上下文信息。</p>

<p><strong>第一轮——初始生成</strong>（简单，无反馈）：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">system_prompt</span> <span class="o">=</span> <span class="s">"""You are a coding assistant. Output ONLY a single 
fenced Python code block that defines the function 
is_valid_password(password: str) -&gt; bool. No prose or comments."""</span>
</code></pre></div></div>

<p><strong>第二轮——反思</strong>（给出之前的代码 + 哪里出了问题）：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">reflexion_prompt</span> <span class="o">=</span> <span class="s">"""You are a coding assistant performing 
self-correction (reflexion).

You will be given:
1. A previous implementation that failed some test cases.
2. A list of failing test cases with expected vs. actual results 
   and specific validation rules that failed.

Your job:
- Read the failure report carefully.
- Identify exactly which checks were wrong, missed, or inverted.
- Produce a corrected implementation."""</span>
</code></pre></div></div>

<p>反思提示会收到实际的失败输出作为上下文——比如：<code class="language-plaintext highlighter-rouge">"Input: Password1 → expected True, got False. Failing checks: missing special"</code>。第一轮往往会漏掉边界情况。但在看到<em>具体</em>哪些测试失败了以及<em>为什么</em>之后，第二轮就能搞定。</p>

<p><strong>为什么有效：</strong> 反思模仿了人类调试的方式：写代码 → 跑测试 → 看报错 → 修复。模型不需要第一次就写对，它只需要在具体反馈的引导下<em>最终</em>写对就行。</p>

<p><strong>需要注意的坑：</strong></p>
<ul>
  <li><strong>反馈质量至关重要。</strong> “错了，再试一次”毫无用处。”输入 ‘Password1’ 返回了 True，但应该返回 False，因为缺少特殊字符”——这才是可操作的反馈。</li>
  <li><strong>一轮迭代可能不够。</strong> 复杂的 bug 可能需要多轮反思。但轮数太多，模型可能会原地打转。</li>
  <li><strong>模型可能矫枉过正。</strong> 它可能修好了失败的测试，却搞坏了之前通过的。一定要重新运行所有测试，而不只是失败的那些。</li>
</ul>

<h2 id="6-工具调用tool-calling用真工具别靠猜">6. 工具调用（Tool Calling）：”用真工具，别靠猜”</h2>

<p><strong>核心思路：</strong> 与其让模型去计算或猜测，不如让它调用外部工具（API、代码解释器、数据库），用真实的结果来工作。</p>

<p>我的作业要求模型调用一个解析源文件的 Python 函数：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">system_prompt</span> <span class="o">=</span> <span class="s">"""You are a tool-calling assistant. 
Respond with ONLY a JSON object:
{"tool": "&lt;tool_name&gt;", "args": {&lt;arguments&gt;}}

Available tools:
[
  {
    "name": "output_every_func_return_type",
    "description": "Parse a Python file and return 
    function_name: return_type for each function.",
    "parameters": {
      "file_path": { "type": "string" }
    }
  }
]"""</span>
</code></pre></div></div>

<p>模型输出一个结构化的工具调用；代码<em>真正执行</em>这个工具，并返回真实结果。</p>

<p><strong>为什么有效：</strong> 这就是我在<a href="/zh/blog/2026/05/llm-right-answer-wrong-reasoning/">第二篇博客</a>中提到的”假装执行代码”问题的解决方案。模型不再<em>假装</em>运行代码，而是<em>真正</em>运行代码。不再<em>猜测</em> API 返回什么，而是<em>实际调用</em> API。真实数据，真实结果，没有幻觉。</p>

<p><strong>需要注意的坑：</strong></p>
<ul>
  <li><strong>提示格式必须严格。</strong> 模型需要输出你的代码能解析的有效 JSON。JSON 外面多一个字就会导致解析失败。对输出格式要非常明确。</li>
  <li><strong>安全性很重要。</strong> 如果模型可以调用任意工具，它就可能造成破坏。一定要对工具调用进行验证和沙箱隔离（Sandboxing）。</li>
  <li><strong>不是所有模型都原生支持。</strong> 小模型可能很难生成结构化的 JSON 输出。大模型（GPT-4、Claude）有内置的工具调用支持。</li>
</ul>

<h2 id="这些技巧为什么会存在">这些技巧为什么会存在？</h2>

<p>所有六种技巧都在解决同一个根本问题：<strong>大语言模型是文本预测器，不是推理器。</strong> 它们根据上下文预测最可能的下一个 token。每一种提示技巧，本质上都是一种<em>工程化地构造上下文</em>的方式，让最可能的下一个 token 同时也是正确的那个。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">技巧</th>
      <th style="text-align: left">核心策略</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">少样本提示（Few-Shot）</td>
      <td style="text-align: left">建立一个模型可以延续的模式</td>
    </tr>
    <tr>
      <td style="text-align: left">思维链（Chain-of-Thought）</td>
      <td style="text-align: left">创建引导答案的中间上下文</td>
    </tr>
    <tr>
      <td style="text-align: left">自洽性（Self-Consistency）</td>
      <td style="text-align: left">通过聚合多次尝试来降低方差</td>
    </tr>
    <tr>
      <td style="text-align: left">检索增强生成（RAG）</td>
      <td style="text-align: left">用真实数据替代猜测</td>
    </tr>
    <tr>
      <td style="text-align: left">反思（Reflexion）</td>
      <td style="text-align: left">利用反馈逐步缩小到正确答案</td>
    </tr>
    <tr>
      <td style="text-align: left">工具调用（Tool Calling）</td>
      <td style="text-align: left">对需要计算的任务，直接跳过生成环节</td>
    </tr>
  </tbody>
</table>

<p>这个递进关系讲述了一个故事：我们一开始寄希望于模型能”直接搞定”，然后逐渐给它更多的结构、更多的数据、更多的工具——因为光靠希望不是策略。</p>

<h2 id="自己动手试试">自己动手试试</h2>

<p>想练习提示工程吗？这里有几个优秀的资源：</p>

<p><strong><a href="https://www.promptingguide.ai/">Prompting Guide</a></strong> — 一个全面的开源参考指南，涵盖从零样本到高级智能体模式的所有技巧。非常适合作为速查手册。</p>

<p><strong><a href="https://learnprompting.org/">Learn Prompting</a></strong> — 一个互动课程，有动手练习。免费且结构清晰，适合从入门到进阶的所有用户。</p>

<p><strong><a href="https://github.com/anthropics/prompt-eng-interactive-tutorial">Anthropic’s Interactive Tutorial</a></strong> — 基于 Jupyter notebook 的课程，每节课底部都有内置的实验环境，可以实时动手。</p>

<h2 id="关键收获">关键收获</h2>

<ol>
  <li><strong>提示就是程序。</strong> 你怎么问，往往比你用哪个模型更重要。</li>
  <li><strong>从简单开始，按需升级。</strong> 少样本提示能搞定大多数任务；只有当简单提示失败时，才去用思维链、RAG 或工具调用。</li>
  <li><strong>每种技巧都有代价。</strong> 更多 token、更多 API 调用、更高复杂度。要根据任务的重要程度来选择技巧。</li>
  <li><strong>永远验证输出。</strong> 没有任何技巧能完全消除幻觉——它们只是降低了概率。</li>
  <li><strong>组合使用。</strong> 真正的威力来自混合搭配：RAG + 思维链 + 工具调用，这就是生产级 AI 系统的工作方式。</li>
</ol>

<p style="font-size: 1.4em; font-style: italic; text-align: center; margin: 2rem 0;">每一个提示技巧，都不过是我们早已拥有却遗忘了的沟通能力。</p>

<hr />

<h2 id="参考资源">参考资源</h2>

<ul>
  <li><a href="https://www.promptingguide.ai/">Prompt Engineering Guide</a></li>
  <li><a href="https://learnprompting.org/">Learn Prompting — Free Interactive Course</a></li>
  <li><a href="https://github.com/anthropics/prompt-eng-interactive-tutorial">Anthropic Interactive Prompt Engineering Tutorial (GitHub)</a></li>
  <li><a href="https://platform.openai.com/docs/guides/prompt-engineering">OpenAI Prompt Engineering Best Practices</a></li>
  <li><a href="https://arxiv.org/abs/2203.11171">Self-Consistency Improves Chain of Thought Reasoning (arXiv)</a></li>
  <li><a href="https://arxiv.org/abs/2303.11366">Reflexion: Language Agents with Verbal Reinforcement Learning (arXiv)</a></li>
  <li><a href="https://arxiv.org/abs/2201.11903">Chain-of-Thought Prompting Elicits Reasoning in LLMs (arXiv)</a></li>
  <li><a href="https://arxiv.org/abs/2005.11401">Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks (arXiv)</a></li>
</ul>]]></content><author><name>Zhipeng Bao</name></author><category term="AI" /><category term="LLM" /><category term="prompt-engineering" /><category term="tutorial" /><category term="techniques" /><summary type="html"><![CDATA[同一个模型，同一个问题，答案却天差地别。我发现，一个没用的 AI 回答和一个惊艳的回答之间的差距，往往和模型本身无关——关键在于你怎么问。]]></summary></entry><entry xml:lang="en"><title type="html">Right Answer, Wrong Reasoning: Can LLMs Actually Think?</title><link href="https://hzxbzp.github.io/blog/2026/05/llm-right-answer-wrong-reasoning/" rel="alternate" type="text/html" title="Right Answer, Wrong Reasoning: Can LLMs Actually Think?" /><published>2026-05-17T00:00:00+00:00</published><updated>2026-05-17T00:00:00+00:00</updated><id>https://hzxbzp.github.io/blog/2026/05/llm-right-answer-wrong-reasoning</id><content type="html" xml:base="https://hzxbzp.github.io/blog/2026/05/llm-right-answer-wrong-reasoning/"><![CDATA[<p>I asked an AI to solve a math problem. It wrote Python code, “ran” it, showed intermediate results, and arrived at the correct answer. Impressive — until I looked closer and realized the reasoning was a performance, not a proof.</p>

<h2 id="the-story-a-math-problem-that-fooled-me">The Story: A Math Problem That Fooled Me</h2>

<p>For another CS146S assignment, I needed an LLM to solve this using chain-of-thought prompting:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>What is 3^{12345} (mod 100)?
</code></pre></div></div>

<p>In plain English: what are the last two digits of 3 raised to the power of 12345? The answer is <strong>43</strong>.</p>

<p>Here’s the setup:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">ollama</span> <span class="kn">import</span> <span class="n">chat</span>

<span class="n">system_prompt</span> <span class="o">=</span> <span class="s">"""You solve user's problem. Think step by step. 
Output the reasoning trace and the final answer."""</span>

<span class="n">user_prompt</span> <span class="o">=</span> <span class="s">"""
Solve this problem, then give the final answer on the last line 
as "Answer: &lt;number&gt;".

what is 3^{12345} (mod 100)?
"""</span>

<span class="n">response</span> <span class="o">=</span> <span class="n">chat</span><span class="p">(</span>
    <span class="n">model</span><span class="o">=</span><span class="s">"llama3.1:8b"</span><span class="p">,</span>
    <span class="n">messages</span><span class="o">=</span><span class="p">[</span>
        <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"system"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">system_prompt</span><span class="p">},</span>
        <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"user"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">user_prompt</span><span class="p">},</span>
    <span class="p">],</span>
    <span class="n">options</span><span class="o">=</span><span class="p">{</span><span class="s">"temperature"</span><span class="p">:</span> <span class="mf">0.3</span><span class="p">},</span>
<span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="n">content</span><span class="p">)</span>
</code></pre></div></div>

<p>Llama 3.1 responded with a beautifully structured answer: it wrote Python code, displayed “outputs,” identified a pattern, and arrived at 43. On the surface, it looked like rigorous step-by-step problem-solving.</p>

<p>But something was off.</p>

<h2 id="what-llama-showed-me">What Llama Showed Me</h2>

<p>The model’s response looked roughly like this:</p>

<p><strong>Step 1:</strong> Compute the first few powers of 3 mod 100 to find a pattern.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">powers</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
    <span class="n">power</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">modulus</span><span class="p">)</span>
    <span class="n">powers</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">power</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">powers</span><span class="p">)</span>  <span class="c1"># Output: [3, 9, 27, 81, 43, 29, 87, 61, 83, 49]
</span></code></pre></div></div>

<p><strong>Step 2:</strong> “From the output, we can see the pattern repeats every 20 powers.”</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">simplified_exponent</span> <span class="o">=</span> <span class="mi">12345</span> <span class="o">%</span> <span class="mi">20</span>  <span class="c1"># Output: 5
</span></code></pre></div></div>

<p><strong>Step 3:</strong> Calculate the final result.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">result</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">100</span><span class="p">)</span>  <span class="c1"># Output: 43
</span></code></pre></div></div>

<p><strong>Answer: 43</strong> — Correct!</p>

<p>Looks solid, right? Code, outputs, pattern recognition, final answer. But here’s the thing…</p>

<h2 id="three-things-that-were-wrong">Three Things That Were Wrong</h2>

<p><img src="/assets/images/blog/llm-reasoning-vs-real.svg" alt="What Llama showed vs what actually happened" /></p>

<h3 id="1-it-never-ran-the-code">1. It never ran the code</h3>

<p>Llama 3.1 is a text-generation model. It doesn’t have a Python interpreter. Those “outputs” after each <code class="language-plaintext highlighter-rouge">print()</code> statement? The model <em>generated</em> them as text — it predicted what the output <em>should</em> look like based on patterns in its training data.</p>

<p>For simple calculations, these predictions happen to be correct. But the model wasn’t computing — it was <em>performing</em> computation.</p>

<h3 id="2-the-logic-has-a-gap">2. The logic has a gap</h3>

<p>It showed 10 values, then claimed the pattern repeats every <strong>20</strong>. But you can’t deduce a period of 20 from only 10 data points. To actually prove this, you’d need to verify that 3^20 mod 100 = 1. Llama skipped that entirely.</p>

<h3 id="3-it-probably-knew-the-answer-first">3. It probably knew the answer first</h3>

<p>The most likely explanation: Llama had seen similar modular arithmetic problems in its training data. It already “knew” the period is 20 (related to Euler’s totient function). So it constructed a plausible-looking derivation <em>around</em> an answer it had already retrieved — not <em>toward</em> an answer it was discovering.</p>

<p>This is <strong>post-hoc rationalization</strong>: starting from the conclusion and building a story backwards.</p>

<h2 id="the-real-question-do-llms-actually-reason">The Real Question: Do LLMs Actually Reason?</h2>

<p>This experience points to a deeper issue that researchers are actively studying: <strong>when an LLM shows you its “thinking,” is it showing you the actual process that led to the answer, or a fabricated narrative?</strong></p>

<p>Recent research calls this the problem of <strong>unfaithful chain-of-thought</strong>. A 2025 study titled “Chain-of-Thought Reasoning In The Wild Is Not Always Faithful” found that even frontier models produce post-hoc rationalizations — GPT-4o-mini does it about 13% of the time, and even the best models aren’t fully immune.</p>

<p>The core tension is this: LLMs are trained to predict the next token, not to reason logically. When we prompt them to “think step by step,” they generate text that <em>looks like</em> reasoning. Sometimes it <em>is</em> genuine reasoning. Sometimes it’s pattern-matching dressed up in the format of logic.</p>

<p>And here’s what makes it dangerous: <strong>you can’t always tell the difference by reading the output.</strong> The fabricated reasoning looks just as convincing as the real thing.</p>

<h2 id="why-this-happens">Why This Happens</h2>

<h3 id="llms-are-text-predictors-not-logic-engines">LLMs are text predictors, not logic engines</h3>

<p>At their core, language models predict: “given everything so far, what token comes next?” They’ve seen millions of math solutions in training, so they know the <em>format</em> of a proof. But knowing the format doesn’t mean following the logic.</p>

<h3 id="training-data-creates-shortcuts">Training data creates shortcuts</h3>

<p>Llama has seen thousands of modular arithmetic problems. It learned that “modular exponentiation → find the period → reduce the exponent.” So it retrieves and applies this template. When the template fits, the answer is correct. When it doesn’t, you get confident nonsense.</p>

<h3 id="show-your-work-doesnt-guarantee-honest-work">“Show your work” doesn’t guarantee honest work</h3>

<p>Chain-of-thought prompting (“think step by step”) was designed to improve accuracy by forcing the model to lay out intermediate steps. And it does improve accuracy — but research shows the displayed steps don’t always reflect the model’s actual “decision process.” The model may have arrived at the answer through a completely different internal path, then generated a plausible explanation after the fact.</p>

<h2 id="what-can-we-do-about-it">What Can We Do About It?</h2>

<h3 id="1-give-llms-real-tools">1. Give LLMs real tools</h3>

<p>The most direct fix: let models actually execute code instead of pretending to. GPT-4 with Code Interpreter, Claude with tool use — these models can run Python for real. When Llama “runs” code in its head, it’s guessing. When a model with tool access runs code, it’s computing.</p>

<h3 id="2-verify-the-reasoning-not-just-the-answer">2. Verify the reasoning, not just the answer</h3>

<p>Don’t stop at “is the answer correct?” Ask: “does each step logically follow from the previous one?” In my Llama example, checking the 10-to-20 logical gap would have revealed the problem immediately.</p>

<h3 id="3-self-consistency-checking">3. Self-consistency checking</h3>

<p>Run the same prompt multiple times with different temperatures. If the model produces different reasoning paths but the same answer, the answer is likely correct. If the reasoning paths contradict each other, something’s wrong. Research shows this can substantially improve reliability.</p>

<h3 id="4-cross-verify-with-different-approaches">4. Cross-verify with different approaches</h3>

<p>Ask the model to solve the same problem in two different ways. If both approaches agree, confidence goes up. If they disagree, at least one reasoning chain is unfaithful.</p>

<h3 id="5-treat-llm-output-as-a-draft-not-a-proof">5. Treat LLM output as a draft, not a proof</h3>

<p>This is the most important mindset shift. LLM reasoning is a <em>starting point</em> for human verification, not a substitute for it. The model gives you a plausible approach; you verify whether it actually holds.</p>

<h2 id="key-takeaways">Key Takeaways</h2>

<ol>
  <li><strong>Correct answers don’t guarantee correct reasoning</strong> — an LLM can get the right answer for the wrong reasons</li>
  <li><strong>LLMs perform reasoning, not always practice it</strong> — the “thinking” you see may be reconstructed after the fact</li>
  <li><strong>Fake code execution is real</strong> — without tool access, models generate plausible-looking but unverified outputs</li>
  <li><strong>Chain-of-thought is powerful but imperfect</strong> — it improves accuracy while creating a false sense of transparency</li>
  <li><strong>Always verify the process, not just the result</strong> — especially for math, logic, and any high-stakes reasoning</li>
</ol>

<p style="font-size: 1.4em; font-style: italic; text-align: center; margin: 2rem 0;">The best liars don't get the facts wrong — they get the reasoning wrong.</p>

<hr />

<h2 id="useful-resources">Useful Resources</h2>

<ul>
  <li><a href="https://arxiv.org/abs/2503.08679">Chain-of-Thought Reasoning In The Wild Is Not Always Faithful (arXiv, 2025)</a></li>
  <li><a href="https://arxiv.org/abs/2305.04388">Language Models Don’t Always Say What They Think (arXiv, 2023)</a></li>
  <li><a href="https://arxiv.org/abs/2203.11171">Self-Consistency Improves Chain of Thought Reasoning (arXiv)</a></li>
  <li><a href="https://explore.n1n.ai/blog/llm-cot-faithfulness-research-2026-03-30">Chain of Thought Faithfulness: Why LLM Reasoning Is Often a Narrative (n1n.ai)</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Stochastic_parrot">Stochastic Parrot — Wikipedia</a></li>
  <li><a href="https://simonwillison.net/2025/Mar/2/hallucinations-in-code/">Hallucinations in Code Are the Least Dangerous Form of LLM Mistakes (Simon Willison)</a></li>
</ul>]]></content><author><name>Zhipeng Bao</name></author><category term="AI" /><category term="LLM" /><category term="reasoning" /><category term="chain-of-thought" /><category term="tutorial" /><summary type="html"><![CDATA[I asked an AI to solve a math problem. It wrote Python code, “ran” it, showed intermediate results, and arrived at the correct answer. Impressive — until I looked closer and realized the reasoning was a performance, not a proof.]]></summary></entry><entry xml:lang="zh"><title type="html">答案对了，推理错了：LLM 真的会思考吗？</title><link href="https://hzxbzp.github.io/zh/blog/2026/05/llm-right-answer-wrong-reasoning/" rel="alternate" type="text/html" title="答案对了，推理错了：LLM 真的会思考吗？" /><published>2026-05-17T00:00:00+00:00</published><updated>2026-05-17T00:00:00+00:00</updated><id>https://hzxbzp.github.io/zh/blog/2026/05/zh-llm-right-answer-wrong-reasoning</id><content type="html" xml:base="https://hzxbzp.github.io/zh/blog/2026/05/llm-right-answer-wrong-reasoning/"><![CDATA[<p>我让一个 AI 解一道数学题。它写了 Python 代码，”运行”了一下，展示了中间结果，最后得出了正确答案。看起来很厉害——直到我仔细一看，发现那个推理过程是在”演戏”，而不是在”证明”。</p>

<h2 id="故事的起因一道把我骗了的数学题">故事的起因：一道把我骗了的数学题</h2>

<p>在另一次 CS146S 的作业中，我需要用 <strong>思维链（Chain-of-Thought）</strong> 提示法让 LLM 解这道题：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>What is 3^{12345} (mod 100)?
</code></pre></div></div>

<p>翻译成人话就是：3 的 12345 次方，最后两位数字是什么？答案是 <strong>43</strong>。</p>

<p>下面是代码设置：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">ollama</span> <span class="kn">import</span> <span class="n">chat</span>

<span class="n">system_prompt</span> <span class="o">=</span> <span class="s">"""You solve user's problem. Think step by step. 
Output the reasoning trace and the final answer."""</span>

<span class="n">user_prompt</span> <span class="o">=</span> <span class="s">"""
Solve this problem, then give the final answer on the last line 
as "Answer: &lt;number&gt;".

what is 3^{12345} (mod 100)?
"""</span>

<span class="n">response</span> <span class="o">=</span> <span class="n">chat</span><span class="p">(</span>
    <span class="n">model</span><span class="o">=</span><span class="s">"llama3.1:8b"</span><span class="p">,</span>
    <span class="n">messages</span><span class="o">=</span><span class="p">[</span>
        <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"system"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">system_prompt</span><span class="p">},</span>
        <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"user"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">user_prompt</span><span class="p">},</span>
    <span class="p">],</span>
    <span class="n">options</span><span class="o">=</span><span class="p">{</span><span class="s">"temperature"</span><span class="p">:</span> <span class="mf">0.3</span><span class="p">},</span>
<span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="n">content</span><span class="p">)</span>
</code></pre></div></div>

<p>Llama 3.1 给出了一个结构漂亮的回答：它写了 Python 代码，展示了”输出结果”，识别出了一个规律，最终得出了 43。从表面上看，这是一个严谨的、逐步推导的解题过程。</p>

<p>但有些地方不太对劲。</p>

<h2 id="llama-给我看了什么">Llama 给我看了什么</h2>

<p>模型的回答大概是这样的：</p>

<p><strong>第 1 步：</strong> 计算前几个 3 的幂对 100 取模的结果，找规律。</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">powers</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
    <span class="n">power</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">modulus</span><span class="p">)</span>
    <span class="n">powers</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">power</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">powers</span><span class="p">)</span>  <span class="c1"># Output: [3, 9, 27, 81, 43, 29, 87, 61, 83, 49]
</span></code></pre></div></div>

<p><strong>第 2 步：</strong> “从输出结果可以看出，这个模式每 20 个幂次重复一次。”</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">simplified_exponent</span> <span class="o">=</span> <span class="mi">12345</span> <span class="o">%</span> <span class="mi">20</span>  <span class="c1"># Output: 5
</span></code></pre></div></div>

<p><strong>第 3 步：</strong> 计算最终结果。</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">result</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">100</span><span class="p">)</span>  <span class="c1"># Output: 43
</span></code></pre></div></div>

<p><strong>Answer: 43</strong> ——正确！</p>

<p>看起来很靠谱对吧？代码、输出、模式识别、最终答案，一套流程下来很完整。但问题是……</p>

<h2 id="三个错误">三个错误</h2>

<p><img src="/assets/images/blog/llm-reasoning-vs-real.svg" alt="What Llama showed vs what actually happened" /></p>

<h3 id="1-它根本没运行代码">1. 它根本没运行代码</h3>

<p>Llama 3.1 是一个文本生成模型，它没有 Python 解释器。那些 <code class="language-plaintext highlighter-rouge">print()</code> 语句后面的”输出结果”？其实是模型根据训练数据中的模式，<em>预测</em>出来的文本——它猜的是输出<em>应该</em>长什么样。</p>

<p>对于简单的计算，这些预测恰好是对的。但模型并不是在<strong>计算（computing）</strong>——它是在<strong>表演（performing）</strong>计算。</p>

<h3 id="2-逻辑有漏洞">2. 逻辑有漏洞</h3>

<p>它展示了 10 个值，然后声称模式每 <strong>20</strong> 个一循环。但你不可能从 10 个数据点推导出周期是 20。要真正证明这一点，你需要验证 3^20 mod 100 = 1。Llama 完全跳过了这一步。</p>

<h3 id="3-它很可能一开始就知道答案">3. 它很可能一开始就知道答案</h3>

<p>最可能的解释是：Llama 在训练数据中见过类似的模运算题。它已经”知道”周期是 20（这和<strong>欧拉函数（Euler’s totient function）</strong>有关）。所以它是围绕一个已经检索到的答案，<em>反向构造</em>了一个看起来合理的推导过程——而不是<em>正向推导</em>出一个新发现的答案。</p>

<p>这就是<strong>事后合理化（post-hoc rationalization）</strong>：从结论出发，反向编一个故事。</p>

<h2 id="真正的问题llm-到底会不会推理">真正的问题：LLM 到底会不会推理？</h2>

<p>这个经历指向了一个研究人员正在积极研究的更深层问题：<strong>当一个 LLM 向你展示它的”思考过程”时，它展示的是真正导致答案产生的过程，还是一个编造出来的叙事？</strong></p>

<p>最近的研究把这称为<strong>不忠实的思维链（unfaithful chain-of-thought）</strong>问题。2025 年一项题为”Chain-of-Thought Reasoning In The Wild Is Not Always Faithful”的研究发现，即使是最前沿的模型也会产生事后合理化——GPT-4o-mini 大约有 13% 的概率这样做，就连最好的模型也不能完全幸免。</p>

<p>核心矛盾在于：LLM 被训练来预测下一个 <strong>token</strong>，而不是进行逻辑推理。当我们提示它”逐步思考”时，它生成的是<em>看起来像</em>推理的文本。有时候这<em>确实是</em>真正的推理。有时候只是伪装成逻辑格式的模式匹配。</p>

<p>而真正危险的地方在于：<strong>你不能总是通过阅读输出来分辨真假。</strong> 编造出来的推理看起来和真正的推理一样令人信服。</p>

<h2 id="为什么会这样">为什么会这样</h2>

<h3 id="llm-是文本预测器不是逻辑引擎">LLM 是文本预测器，不是逻辑引擎</h3>

<p>归根结底，语言模型做的事情就是预测：”根据到目前为止的所有内容，下一个 token 是什么？”它们在训练中见过几百万个数学解题过程，所以它们知道证明的<em>格式</em>。但知道格式不等于遵循逻辑。</p>

<h3 id="训练数据制造了捷径">训练数据制造了捷径</h3>

<p>Llama 见过成千上万的模运算问题。它学到了”模幂运算 → 找周期 → 简化指数”这个模板。所以它检索并套用了这个模板。当模板恰好适用时，答案就是对的。当不适用时，你得到的就是一本正经的胡说八道。</p>

<h3 id="写出你的解题步骤不等于诚实地展示解题步骤">“写出你的解题步骤”不等于”诚实地展示解题步骤”</h3>

<p><strong>思维链提示法（Chain-of-thought prompting）</strong>（”逐步思考”）的设计初衷是通过迫使模型展示中间步骤来提高准确率。它确实提高了准确率——但研究表明，展示出来的步骤并不总是反映模型实际的”决策过程”。模型可能是通过一条完全不同的内部路径得出了答案，然后在事后生成了一个看起来合理的解释。</p>

<h2 id="我们能做什么">我们能做什么？</h2>

<h3 id="1-给-llm-真正的工具">1. 给 LLM 真正的工具</h3>

<p>最直接的解决办法：让模型真正执行代码，而不是假装执行。GPT-4 带 <strong>Code Interpreter</strong>、Claude 带 <strong>tool use</strong>——这些模型可以真正运行 Python。当 Llama 在”脑子里”运行代码时，它是在猜。当有工具访问权限的模型运行代码时，它是在计算。</p>

<h3 id="2-验证推理过程而不只是答案">2. 验证推理过程，而不只是答案</h3>

<p>不要停留在”答案对不对？”这个问题上。要问：”每一步是否从上一步逻辑推导而来？”在我的 Llama 例子中，检查”从 10 个值跳到周期 20”这个逻辑漏洞就能立刻发现问题。</p>

<h3 id="3-自洽性检查self-consistency-checking">3. 自洽性检查（Self-consistency checking）</h3>

<p>用不同的 temperature 多次运行同一个 prompt。如果模型产生了不同的推理路径但得出相同的答案，那答案很可能是对的。如果推理路径互相矛盾，那就有问题了。研究表明这种方法可以显著提高可靠性。</p>

<h3 id="4-用不同方法交叉验证">4. 用不同方法交叉验证</h3>

<p>让模型用两种不同的方法解同一道题。如果两种方法得出一致的结论，信心就提高了。如果不一致，至少有一条推理链是不忠实的。</p>

<h3 id="5-把-llm-的输出当草稿而不是证明">5. 把 LLM 的输出当草稿，而不是证明</h3>

<p>这是最重要的思维转变。LLM 的推理是人类验证的<em>起点</em>，而不是替代品。模型给你一个看起来合理的方法；你来验证它是否真的成立。</p>

<h2 id="关键要点">关键要点</h2>

<ol>
  <li><strong>正确的答案不等于正确的推理</strong> ——LLM 可以因为错误的理由得出正确的答案</li>
  <li><strong>LLM 在表演推理，而不总是在实践推理</strong> ——你看到的”思考过程”可能是事后重构的</li>
  <li><strong>假装执行代码是真实存在的问题</strong> ——没有工具访问权限时，模型会生成看起来合理但未经验证的输出</li>
  <li><strong>思维链强大但不完美</strong> ——它提高了准确率，同时也制造了虚假的透明感</li>
  <li><strong>永远要验证过程，而不只是结果</strong> ——尤其是在数学、逻辑和任何高风险推理场景中</li>
</ol>

<p style="font-size: 1.4em; font-style: italic; text-align: center; margin: 2rem 0;">最高明的骗子不会弄错事实——他们只是编造了推理过程。</p>

<hr />

<h2 id="参考资源">参考资源</h2>

<ul>
  <li><a href="https://arxiv.org/abs/2503.08679">Chain-of-Thought Reasoning In The Wild Is Not Always Faithful (arXiv, 2025)</a></li>
  <li><a href="https://arxiv.org/abs/2305.04388">Language Models Don’t Always Say What They Think (arXiv, 2023)</a></li>
  <li><a href="https://arxiv.org/abs/2203.11171">Self-Consistency Improves Chain of Thought Reasoning (arXiv)</a></li>
  <li><a href="https://explore.n1n.ai/blog/llm-cot-faithfulness-research-2026-03-30">Chain of Thought Faithfulness: Why LLM Reasoning Is Often a Narrative (n1n.ai)</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Stochastic_parrot">Stochastic Parrot — Wikipedia</a></li>
  <li><a href="https://simonwillison.net/2025/Mar/2/hallucinations-in-code/">Hallucinations in Code Are the Least Dangerous Form of LLM Mistakes (Simon Willison)</a></li>
</ul>]]></content><author><name>Zhipeng Bao</name></author><category term="AI" /><category term="LLM" /><category term="reasoning" /><category term="chain-of-thought" /><category term="tutorial" /><summary type="html"><![CDATA[我让一个 AI 解一道数学题。它写了 Python 代码，”运行”了一下，展示了中间结果，最后得出了正确答案。看起来很厉害——直到我仔细一看，发现那个推理过程是在”演戏”，而不是在”证明”。]]></summary></entry><entry xml:lang="en"><title type="html">Why Can’t AI Spell Backwards? The Secret of Tokenization</title><link href="https://hzxbzp.github.io/blog/2026/05/why-llms-cant-reverse-words/" rel="alternate" type="text/html" title="Why Can’t AI Spell Backwards? The Secret of Tokenization" /><published>2026-05-16T00:00:00+00:00</published><updated>2026-05-16T00:00:00+00:00</updated><id>https://hzxbzp.github.io/blog/2026/05/why-llms-cant-reverse-words</id><content type="html" xml:base="https://hzxbzp.github.io/blog/2026/05/why-llms-cant-reverse-words/"><![CDATA[<p>Have you ever asked ChatGPT to reverse a word and gotten a wrong answer? I did — and it led me down a fascinating rabbit hole about how AI actually “reads” text.</p>

<h2 id="the-story-a-homework-assignment-gone-wrong">The Story: A Homework Assignment Gone Wrong</h2>

<p>I was working on an assignment for my CS146S course (The Modern Software Developer) where the task was straightforward: <strong>get an LLM to reverse the letters of a word</strong>. Sounds simple, right?</p>

<p>Here’s the code I wrote, using k-shot prompting with Llama 3.1:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">ollama</span> <span class="kn">import</span> <span class="n">chat</span>

<span class="n">YOUR_SYSTEM_PROMPT</span> <span class="o">=</span> <span class="s">"""You reverse the letters of a word. 
Output ONLY the reversed word, with no explanation, no punctuation, 
and no other text.

Word: httpstatus
Reversed: sutatsptth

Word: hello
Reversed: olleh

Word: httpstatus
Reversed: sutatsptth"""</span>

<span class="n">USER_PROMPT</span> <span class="o">=</span> <span class="s">"""
Reverse the order of letters in the following word. 
Only output the reversed word, no other text:

httpstatus
"""</span>

<span class="n">EXPECTED_OUTPUT</span> <span class="o">=</span> <span class="s">"sutatsptth"</span>

<span class="n">response</span> <span class="o">=</span> <span class="n">chat</span><span class="p">(</span>
    <span class="n">model</span><span class="o">=</span><span class="s">"llama3.1:8b"</span><span class="p">,</span>
    <span class="n">messages</span><span class="o">=</span><span class="p">[</span>
        <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"system"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">YOUR_SYSTEM_PROMPT</span><span class="p">},</span>
        <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"user"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">USER_PROMPT</span><span class="p">},</span>
    <span class="p">],</span>
    <span class="n">options</span><span class="o">=</span><span class="p">{</span><span class="s">"temperature"</span><span class="p">:</span> <span class="mf">0.5</span><span class="p">},</span>
<span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="n">content</span><span class="p">.</span><span class="n">strip</span><span class="p">())</span>
</code></pre></div></div>

<p>Even with <strong>5 examples</strong> showing the correct reversal, the model kept getting it wrong! It would output things like “sutattsptth” or “statustpth” — close, but not right.</p>

<p>This isn’t just a Llama problem. Try asking GPT-4 or Claude to reverse “httpstatus” — they’ll often struggle too. But <strong>why</strong>?</p>

<h2 id="the-answer-llms-dont-see-letters">The Answer: LLMs Don’t See Letters</h2>

<p>Here’s the key insight: <strong>LLMs don’t read text letter by letter like humans do.</strong> They read in chunks called <strong>tokens</strong>.</p>

<p>When you type “httpstatus”, the AI doesn’t see:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>h - t - t - p - s - t - a - t - u - s
</code></pre></div></div>

<p>Instead, it sees something like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[http] [status]
</code></pre></div></div>

<p>That’s it. Two chunks. The individual letters are invisible to the model.</p>

<p><img src="/assets/images/blog/tokenization-example.svg" alt="How tokenization splits &quot;httpstatus&quot; into tokens" /></p>

<h2 id="what-are-tokens">What Are Tokens?</h2>

<p>A <strong>token</strong> is the basic unit of text that an LLM processes. Think of it as the “atom” of language for AI. Tokens can be:</p>

<ul>
  <li>A whole word: <code class="language-plaintext highlighter-rouge">hello</code> → 1 token</li>
  <li>Part of a word: <code class="language-plaintext highlighter-rouge">running</code> → <code class="language-plaintext highlighter-rouge">run</code> + <code class="language-plaintext highlighter-rouge">ning</code> (2 tokens)</li>
  <li>A single character: rare characters might be individual tokens</li>
  <li>Punctuation: <code class="language-plaintext highlighter-rouge">.</code> <code class="language-plaintext highlighter-rouge">!</code> <code class="language-plaintext highlighter-rouge">?</code> are usually their own tokens</li>
  <li>Even spaces: in some tokenizers, spaces are part of the token</li>
</ul>

<p>For example, the sentence “I love AI” might become 3 tokens: <code class="language-plaintext highlighter-rouge">[I]</code> <code class="language-plaintext highlighter-rouge">[love]</code> <code class="language-plaintext highlighter-rouge">[AI]</code>. But “tokenization” might become <code class="language-plaintext highlighter-rouge">[token]</code> <code class="language-plaintext highlighter-rouge">[ization]</code> — two tokens.</p>

<p><strong>Why not just use individual letters?</strong> Because that would be incredibly expensive. The sentence “Hello, how are you?” has 20 characters but only about 6 tokens. Fewer tokens = less computation = faster and cheaper responses.</p>

<h2 id="why-do-llms-need-tokenizers">Why Do LLMs Need Tokenizers?</h2>

<p>Neural networks only understand numbers, not text. So before any text reaches the AI’s “brain,” it must be converted to numbers. The process works like this:</p>

<ol>
  <li><strong>Text</strong> → split into tokens (by the tokenizer)</li>
  <li><strong>Tokens</strong> → mapped to numeric IDs (from a vocabulary)</li>
  <li><strong>Numeric IDs</strong> → processed by the neural network</li>
  <li><strong>Output IDs</strong> → converted back to tokens → text</li>
</ol>

<p>The tokenizer is essentially the AI’s “eyes” — it determines what the model can and cannot see. And just like human eyes have blind spots, <strong>tokenizers have blind spots too</strong>. Letter-level operations fall right into that blind spot.</p>

<h2 id="how-does-tokenization-work-the-bpe-algorithm">How Does Tokenization Work? The BPE Algorithm</h2>

<p>The most popular tokenization method is called <strong>Byte Pair Encoding (BPE)</strong>. It’s surprisingly elegant:</p>

<ol>
  <li>Start with all individual characters as your vocabulary</li>
  <li>Count which pair of adjacent characters appears most frequently in your training data</li>
  <li>Merge that pair into a new token</li>
  <li>Repeat steps 2-3 until you reach your desired vocabulary size</li>
</ol>

<p><img src="/assets/images/blog/bpe-algorithm.svg" alt="BPE algorithm step by step" /></p>

<p>For example, if your training data has lots of English text, “th” appears very frequently, so it gets merged early. Then “the” becomes common, so that gets merged too. Eventually, common words like “the”, “and”, “is” become single tokens, while rare words get split into pieces.</p>

<h3 id="a-concrete-example">A Concrete Example</h3>

<p>Let’s say we’re building a tiny BPE vocabulary from the text “low lower lowest”:</p>

<table>
  <thead>
    <tr>
      <th>Step</th>
      <th>Action</th>
      <th>Vocabulary Change</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>Start with characters</td>
      <td><code class="language-plaintext highlighter-rouge">l, o, w, e, r, s, t</code></td>
    </tr>
    <tr>
      <td>1</td>
      <td>Most frequent pair: <code class="language-plaintext highlighter-rouge">l</code> + <code class="language-plaintext highlighter-rouge">o</code> → merge</td>
      <td>Add <code class="language-plaintext highlighter-rouge">lo</code></td>
    </tr>
    <tr>
      <td>2</td>
      <td>Most frequent pair: <code class="language-plaintext highlighter-rouge">lo</code> + <code class="language-plaintext highlighter-rouge">w</code> → merge</td>
      <td>Add <code class="language-plaintext highlighter-rouge">low</code></td>
    </tr>
    <tr>
      <td>3</td>
      <td>Most frequent pair: <code class="language-plaintext highlighter-rouge">e</code> + <code class="language-plaintext highlighter-rouge">r</code> → merge</td>
      <td>Add <code class="language-plaintext highlighter-rouge">er</code></td>
    </tr>
    <tr>
      <td>4</td>
      <td>Most frequent pair: <code class="language-plaintext highlighter-rouge">e</code> + <code class="language-plaintext highlighter-rouge">s</code> → merge</td>
      <td>Add <code class="language-plaintext highlighter-rouge">es</code></td>
    </tr>
  </tbody>
</table>

<p>After training, “lower” tokenizes as <code class="language-plaintext highlighter-rouge">[low][er]</code> instead of 5 separate characters. Efficient!</p>

<h2 id="different-tokenizers-for-different-models">Different Tokenizers for Different Models</h2>

<p>Not all LLMs use the same tokenizer. Here’s a quick overview:</p>

<p><img src="/assets/images/blog/tokenizer-comparison.svg" alt="Tokenizer comparison table" /></p>

<h3 id="bpe-byte-pair-encoding">BPE (Byte Pair Encoding)</h3>
<ul>
  <li><strong>Used by:</strong> GPT-2, GPT-3, GPT-4, LLaMA, Mistral, Claude</li>
  <li><strong>How it works:</strong> Merges frequent character pairs iteratively</li>
  <li><strong>Variant:</strong> Byte-level BPE starts with 256 byte values instead of characters, handling any language/encoding</li>
</ul>

<h3 id="wordpiece">WordPiece</h3>
<ul>
  <li><strong>Used by:</strong> BERT, DistilBERT, Electra</li>
  <li><strong>How it works:</strong> Similar to BPE, but selects merges based on likelihood (which merge increases the training data probability most)</li>
  <li><strong>Key difference:</strong> Uses <code class="language-plaintext highlighter-rouge">##</code> prefix for sub-word continuations (e.g., “playing” → <code class="language-plaintext highlighter-rouge">play</code> + <code class="language-plaintext highlighter-rouge">##ing</code>)</li>
</ul>

<h3 id="sentencepiece-unigram">SentencePiece (Unigram)</h3>
<ul>
  <li><strong>Used by:</strong> T5, ALBERT, XLNet, Gemma</li>
  <li><strong>How it works:</strong> Starts with a large vocabulary and removes tokens that least affect the overall likelihood</li>
  <li><strong>Key difference:</strong> Treats text as a raw stream (no pre-tokenization by spaces), making it great for multilingual models</li>
</ul>

<h3 id="tiktoken-openais-implementation">Tiktoken (OpenAI’s Implementation)</h3>
<ul>
  <li><strong>GPT-4:</strong> Uses <code class="language-plaintext highlighter-rouge">cl100k_base</code> with ~100K tokens</li>
  <li><strong>GPT-4o:</strong> Uses <code class="language-plaintext highlighter-rouge">o200k_base</code> with ~200K tokens (double the vocabulary!)</li>
  <li>Larger vocabulary = more words as single tokens = faster inference</li>
</ul>

<h2 id="what-about-chinese-tokenization-across-languages">What About Chinese? Tokenization Across Languages</h2>

<p>English has spaces between words. Chinese, Japanese, and Thai don’t. So where do you split “我喜欢人工智能” (I love AI)?</p>

<ul>
  <li><strong>Character-level:</strong> <code class="language-plaintext highlighter-rouge">[我][喜][欢][人][工][智][能]</code> — 7 tokens. Safe but wasteful.</li>
  <li><strong>Word-level:</strong> <code class="language-plaintext highlighter-rouge">[我][喜欢][人工智能]</code> — 3 tokens. Efficient but needs a word segmentation tool first.</li>
  <li><strong>Subword (BPE):</strong> A middle ground — common words like <code class="language-plaintext highlighter-rouge">人工智能</code> become single tokens, rare ones get split.</li>
</ul>

<p>The key breakthrough was <strong>SentencePiece</strong>, which treats input as a raw byte stream instead of assuming spaces between words. This makes it work naturally for any language. That’s why it’s the go-to for multilingual models like T5 and Gemma.</p>

<p>Vocabulary size matters here too. GPT-4o’s tokenizer (o200k_base, 200K tokens) is twice the size of GPT-4’s (cl100k_base, 100K tokens), and a big chunk of those extra tokens went to non-English languages. Try pasting Chinese text into <a href="https://tiktokenizer.vercel.app/">Tiktokenizer</a> — GPT-4o consistently produces fewer tokens for the same input.</p>

<h2 id="try-it-yourself">Try It Yourself!</h2>

<p>Want to see how different models tokenize text? Check out this awesome online tool:</p>

<p><strong><a href="https://tiktokenizer.vercel.app/">Tiktokenizer</a></strong> — paste any text and see exactly how GPT-4, GPT-3.5, and other models break it into tokens.</p>

<p>Try typing “httpstatus” and see how it splits! Then try “h t t p s t a t u s” (with spaces) and notice the difference.</p>

<h2 id="so-how-do-we-get-llms-to-reverse-letters">So How DO We Get LLMs to Reverse Letters?</h2>

<p>Now that we understand the problem, here are effective solutions:</p>

<h3 id="method-1-separate-the-characters-first">Method 1: Separate the Characters First</h3>

<p>The simplest fix — give the model characters it can actually “see”:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Instead of: "Reverse: httpstatus"
# Do this:
</span><span class="n">prompt</span> <span class="o">=</span> <span class="s">"""The letters are: h, t, t, p, s, t, a, t, u, s
Now write them in reverse order, separated by commas:"""</span>
<span class="c1"># Output: s, u, t, a, t, s, p, t, t, h
# Then join: "sutatsptth" ✓
</span></code></pre></div></div>

<h3 id="method-2-chain-of-thought-step-by-step">Method 2: Chain-of-Thought (Step by Step)</h3>

<p>Ask the model to first list the letters, then reverse:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">prompt</span> <span class="o">=</span> <span class="s">"""Reverse the letters in "httpstatus".

Step 1: List each letter with its position:
Position 1: h
Position 2: t
Position 3: t
Position 4: p
Position 5: s
Position 6: t
Position 7: a
Position 8: t
Position 9: u
Position 10: s

Step 2: Now list them from last position to first:"""</span>
</code></pre></div></div>

<h3 id="method-3-use-code-execution">Method 3: Use Code Execution</h3>

<p>The most reliable approach — let the model write and run code:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">prompt</span> <span class="o">=</span> <span class="s">"""Write Python code to reverse the string "httpstatus" 
and output only the result."""</span>

<span class="c1"># Model outputs: print("httpstatus"[::-1])
# Result: sutatsptth ✓
</span></code></pre></div></div>

<h3 id="method-4-fine-tuning-with-character-level-tasks">Method 4: Fine-tuning with Character-Level Tasks</h3>

<p>For production systems, models can be fine-tuned with <strong>Token Internal Position Awareness (TIPA)</strong> — training them on tasks that require knowing character positions within tokens. This is an active research area.</p>

<h2 id="key-takeaways">Key Takeaways</h2>

<ol>
  <li><strong>LLMs don’t see individual letters</strong> — they see tokens (chunks of text)</li>
  <li><strong>Tokenization is a trade-off</strong>: efficiency vs. character-level awareness</li>
  <li><strong>“Simple” text tasks can be hard for AI</strong> when they require letter-level manipulation</li>
  <li><strong>Workarounds exist</strong>: character separation, chain-of-thought, and code execution all help</li>
  <li><strong>The tokenizer is the AI’s “eyes”</strong> — its capabilities and limitations shape what the model can do</li>
</ol>

<p style="font-size: 1.4em; font-style: italic; text-align: center; margin: 2rem 0;">AI doesn't misunderstand language — it never saw it the way we do.</p>

<hr />

<h2 id="useful-resources">Useful Resources</h2>

<ul>
  <li><a href="https://tiktokenizer.vercel.app/">Tiktokenizer — Online Tokenizer Visualization Tool</a></li>
  <li><a href="https://www.youtube.com/watch?v=7xTGNNLPyMI">Let’s Build the GPT Tokenizer — Andrej Karpathy (YouTube)</a></li>
  <li><a href="https://huggingface.co/docs/transformers/en/tokenizer_summary">Hugging Face — Tokenization Algorithms Summary</a></li>
  <li><a href="https://huggingface.co/learn/llm-course/en/chapter6/5">Hugging Face — BPE Tokenization Explained</a></li>
  <li><a href="https://github.com/openai/tiktoken">OpenAI Tiktoken Library (GitHub)</a></li>
  <li><a href="https://nebius.com/blog/posts/how-tokenizers-work-in-ai-models">How Tokenizers Work in AI Models (Nebius)</a></li>
  <li><a href="https://sebastianraschka.com/blog/2025/bpe-from-scratch.html">Sebastian Raschka — Implementing BPE from Scratch</a></li>
  <li><a href="https://arxiv.org/html/2412.18626v1">Why LLMs Struggle to Count Letters (arXiv)</a></li>
</ul>]]></content><author><name>Zhipeng Bao</name></author><category term="AI" /><category term="LLM" /><category term="tokenization" /><category term="NLP" /><category term="tutorial" /><summary type="html"><![CDATA[Have you ever asked ChatGPT to reverse a word and gotten a wrong answer? I did — and it led me down a fascinating rabbit hole about how AI actually “reads” text.]]></summary></entry><entry xml:lang="zh"><title type="html">为什么AI不会倒着拼写？分词的秘密</title><link href="https://hzxbzp.github.io/zh/blog/2026/05/why-llms-cant-reverse-words/" rel="alternate" type="text/html" title="为什么AI不会倒着拼写？分词的秘密" /><published>2026-05-16T00:00:00+00:00</published><updated>2026-05-16T00:00:00+00:00</updated><id>https://hzxbzp.github.io/zh/blog/2026/05/zh-why-llms-cant-reverse-words</id><content type="html" xml:base="https://hzxbzp.github.io/zh/blog/2026/05/why-llms-cant-reverse-words/"><![CDATA[<p>你有没有让ChatGPT把一个单词倒过来拼，结果它给了你一个错误答案？我就遇到过——然后我就掉进了一个关于AI到底是怎么”阅读”文字的兔子洞。</p>

<h2 id="故事的起因一次翻车的作业">故事的起因：一次翻车的作业</h2>

<p>我当时在做CS146S课程（The Modern Software Developer）的作业，任务很简单：<strong>让一个LLM把一个单词的字母倒序排列</strong>。听起来很容易对吧？</p>

<p>下面是我写的代码，用的是k-shot提示和Llama 3.1：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">ollama</span> <span class="kn">import</span> <span class="n">chat</span>

<span class="n">YOUR_SYSTEM_PROMPT</span> <span class="o">=</span> <span class="s">"""You reverse the letters of a word. 
Output ONLY the reversed word, with no explanation, no punctuation, 
and no other text.

Word: httpstatus
Reversed: sutatsptth

Word: hello
Reversed: olleh

Word: httpstatus
Reversed: sutatsptth"""</span>

<span class="n">USER_PROMPT</span> <span class="o">=</span> <span class="s">"""
Reverse the order of letters in the following word. 
Only output the reversed word, no other text:

httpstatus
"""</span>

<span class="n">EXPECTED_OUTPUT</span> <span class="o">=</span> <span class="s">"sutatsptth"</span>

<span class="n">response</span> <span class="o">=</span> <span class="n">chat</span><span class="p">(</span>
    <span class="n">model</span><span class="o">=</span><span class="s">"llama3.1:8b"</span><span class="p">,</span>
    <span class="n">messages</span><span class="o">=</span><span class="p">[</span>
        <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"system"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">YOUR_SYSTEM_PROMPT</span><span class="p">},</span>
        <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"user"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">USER_PROMPT</span><span class="p">},</span>
    <span class="p">],</span>
    <span class="n">options</span><span class="o">=</span><span class="p">{</span><span class="s">"temperature"</span><span class="p">:</span> <span class="mf">0.5</span><span class="p">},</span>
<span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="n">content</span><span class="p">.</span><span class="n">strip</span><span class="p">())</span>
</code></pre></div></div>

<p>即使给了<strong>5个正确示例</strong>，模型还是一直搞错！它会输出类似”sutattsptth”或”statustpth”的东西——接近了，但就是不对。</p>

<p>这不只是Llama的问题。你试试让GPT-4或Claude去反转”httpstatus”——它们也经常会翻车。但<strong>为什么呢</strong>？</p>

<h2 id="答案llm看不到字母">答案：LLM看不到字母</h2>

<p>关键的洞察是：<strong>LLM并不像人类那样逐个字母地阅读文本。</strong> 它们是按”块”来读的，这些块叫做<strong>词元（Token）</strong>。</p>

<p>当你输入”httpstatus”时，AI看到的并不是：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>h - t - t - p - s - t - a - t - u - s
</code></pre></div></div>

<p>它看到的实际上是这样的：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[http] [status]
</code></pre></div></div>

<p>就这样。两个块。模型根本看不到单个字母。</p>

<p><img src="/assets/images/blog/tokenization-example.svg" alt="分词如何将&quot;httpstatus&quot;拆分为词元" /></p>

<h2 id="什么是词元token">什么是词元（Token）？</h2>

<p><strong>词元（Token）</strong> 是LLM处理的基本文本单位。你可以把它想象成AI语言世界里的”原子”。词元可以是：</p>

<ul>
  <li>一个完整的单词：<code class="language-plaintext highlighter-rouge">hello</code> → 1个词元</li>
  <li>单词的一部分：<code class="language-plaintext highlighter-rouge">running</code> → <code class="language-plaintext highlighter-rouge">run</code> + <code class="language-plaintext highlighter-rouge">ning</code>（2个词元）</li>
  <li>单个字符：罕见字符可能是单独的词元</li>
  <li>标点符号：<code class="language-plaintext highlighter-rouge">.</code> <code class="language-plaintext highlighter-rouge">!</code> <code class="language-plaintext highlighter-rouge">?</code> 通常各自是一个词元</li>
  <li>甚至空格：在某些分词器中，空格也是词元的一部分</li>
</ul>

<p>举个例子，句子”I love AI”可能变成3个词元：<code class="language-plaintext highlighter-rouge">[I]</code> <code class="language-plaintext highlighter-rouge">[love]</code> <code class="language-plaintext highlighter-rouge">[AI]</code>。但”tokenization”可能变成 <code class="language-plaintext highlighter-rouge">[token]</code> <code class="language-plaintext highlighter-rouge">[ization]</code>——两个词元。</p>

<p><strong>为什么不直接用单个字母呢？</strong> 因为那样的计算成本太高了。句子”Hello, how are you?”有20个字符，但只有大约6个词元。更少的词元 = 更少的计算 = 更快更便宜的响应。</p>

<h2 id="为什么llm需要分词器tokenizer">为什么LLM需要分词器（Tokenizer）？</h2>

<p>神经网络只认识数字，不认识文字。所以在任何文本到达AI的”大脑”之前，它必须先被转换成数字。整个过程是这样的：</p>

<ol>
  <li><strong>文本</strong> → 被拆分成词元（由分词器完成）</li>
  <li><strong>词元</strong> → 映射为数字ID（来自词汇表）</li>
  <li><strong>数字ID</strong> → 由神经网络处理</li>
  <li><strong>输出ID</strong> → 转换回词元 → 文本</li>
</ol>

<p>分词器本质上就是AI的”眼睛”——它决定了模型能看到什么、看不到什么。就像人的眼睛有盲区一样，<strong>分词器也有盲区</strong>。字母级别的操作恰恰就落在了这个盲区里。</p>

<h2 id="分词是怎么工作的bpe算法">分词是怎么工作的？BPE算法</h2>

<p>最流行的分词方法叫做<strong>字节对编码（Byte Pair Encoding, BPE）</strong>。它的原理出奇地优雅：</p>

<ol>
  <li>从所有单个字符作为你的词汇表开始</li>
  <li>统计训练数据中哪对相邻字符出现频率最高</li>
  <li>将这对字符合并为一个新的词元</li>
  <li>重复第2-3步，直到达到你想要的词汇表大小</li>
</ol>

<p><img src="/assets/images/blog/bpe-algorithm.svg" alt="BPE算法分步演示" /></p>

<p>比如说，如果你的训练数据里有大量英文文本，”th”出现得非常频繁，所以它很早就会被合并。然后”the”变得常见了，于是也被合并。最终，像”the”、”and”、”is”这样的常见词都会变成单个词元，而罕见词则会被拆分成几部分。</p>

<h3 id="一个具体的例子">一个具体的例子</h3>

<p>假设我们要从文本”low lower lowest”构建一个小型BPE词汇表：</p>

<table>
  <thead>
    <tr>
      <th>步骤</th>
      <th>操作</th>
      <th>词汇表变化</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>从单个字符开始</td>
      <td><code class="language-plaintext highlighter-rouge">l, o, w, e, r, s, t</code></td>
    </tr>
    <tr>
      <td>1</td>
      <td>最高频字符对：<code class="language-plaintext highlighter-rouge">l</code> + <code class="language-plaintext highlighter-rouge">o</code> → 合并</td>
      <td>添加 <code class="language-plaintext highlighter-rouge">lo</code></td>
    </tr>
    <tr>
      <td>2</td>
      <td>最高频字符对：<code class="language-plaintext highlighter-rouge">lo</code> + <code class="language-plaintext highlighter-rouge">w</code> → 合并</td>
      <td>添加 <code class="language-plaintext highlighter-rouge">low</code></td>
    </tr>
    <tr>
      <td>3</td>
      <td>最高频字符对：<code class="language-plaintext highlighter-rouge">e</code> + <code class="language-plaintext highlighter-rouge">r</code> → 合并</td>
      <td>添加 <code class="language-plaintext highlighter-rouge">er</code></td>
    </tr>
    <tr>
      <td>4</td>
      <td>最高频字符对：<code class="language-plaintext highlighter-rouge">e</code> + <code class="language-plaintext highlighter-rouge">s</code> → 合并</td>
      <td>添加 <code class="language-plaintext highlighter-rouge">es</code></td>
    </tr>
  </tbody>
</table>

<p>训练完成后，”lower”被分词为 <code class="language-plaintext highlighter-rouge">[low][er]</code>，而不是5个单独的字符。高效！</p>

<h2 id="不同模型的不同分词器">不同模型的不同分词器</h2>

<p>并非所有LLM都使用相同的分词器。下面是一个快速概览：</p>

<p><img src="/assets/images/blog/tokenizer-comparison.svg" alt="分词器对比表" /></p>

<h3 id="bpe字节对编码byte-pair-encoding">BPE（字节对编码，Byte Pair Encoding）</h3>
<ul>
  <li><strong>使用者：</strong> GPT-2、GPT-3、GPT-4、LLaMA、Mistral、Claude</li>
  <li><strong>工作原理：</strong> 迭代地合并高频字符对</li>
  <li><strong>变体：</strong> Byte-level BPE 从256个字节值开始而非字符，可以处理任何语言和编码</li>
</ul>

<h3 id="wordpiece">WordPiece</h3>
<ul>
  <li><strong>使用者：</strong> BERT、DistilBERT、Electra</li>
  <li><strong>工作原理：</strong> 类似BPE，但根据似然度选择合并（哪个合并最能提高训练数据的概率）</li>
  <li><strong>关键区别：</strong> 使用 <code class="language-plaintext highlighter-rouge">##</code> 前缀表示子词的延续部分（如 “playing” → <code class="language-plaintext highlighter-rouge">play</code> + <code class="language-plaintext highlighter-rouge">##ing</code>）</li>
</ul>

<h3 id="sentencepieceunigram">SentencePiece（Unigram）</h3>
<ul>
  <li><strong>使用者：</strong> T5、ALBERT、XLNet、Gemma</li>
  <li><strong>工作原理：</strong> 从一个大词汇表开始，逐步移除对整体似然度影响最小的词元</li>
  <li><strong>关键区别：</strong> 将文本视为原始字节流（不需要先按空格预分词），因此非常适合多语言模型</li>
</ul>

<h3 id="tiktokenopenai的实现">Tiktoken（OpenAI的实现）</h3>
<ul>
  <li><strong>GPT-4：</strong> 使用 <code class="language-plaintext highlighter-rouge">cl100k_base</code>，约10万个词元</li>
  <li><strong>GPT-4o：</strong> 使用 <code class="language-plaintext highlighter-rouge">o200k_base</code>，约20万个词元（词汇量翻倍！）</li>
  <li>更大的词汇表 = 更多单词成为单个词元 = 更快的推理速度</li>
</ul>

<h2 id="中文怎么办跨语言的分词挑战">中文怎么办？跨语言的分词挑战</h2>

<p>英文单词之间有空格。中文、日文和泰文没有。那”我喜欢人工智能”应该从哪里切分呢？</p>

<ul>
  <li><strong>字符级别：</strong> <code class="language-plaintext highlighter-rouge">[我][喜][欢][人][工][智][能]</code> — 7个词元。安全但浪费。</li>
  <li><strong>词级别：</strong> <code class="language-plaintext highlighter-rouge">[我][喜欢][人工智能]</code> — 3个词元。高效但需要先用分词工具。</li>
  <li><strong>子词（BPE）：</strong> 一种折中方案——常见词如 <code class="language-plaintext highlighter-rouge">人工智能</code> 变成单个词元，罕见词则被拆分。</li>
</ul>

<p>关键性的突破是 <strong>SentencePiece</strong>，它把输入当作原始字节流处理，而不是假设单词之间有空格。这使得它可以自然地适用于任何语言。这就是为什么它是T5和Gemma等多语言模型的首选方案。</p>

<p>词汇表大小在这里也很重要。GPT-4o的分词器（o200k_base，20万词元）是GPT-4（cl100k_base，10万词元）的两倍大，其中很大一部分新增词元都给了非英语语言。试着把中文文本粘贴到 <a href="https://tiktokenizer.vercel.app/">Tiktokenizer</a> 里看看——GPT-4o对于同样的输入一致地产生更少的词元。</p>

<h2 id="自己动手试试">自己动手试试！</h2>

<p>想看看不同的模型是怎么分词的？试试这个很棒的在线工具：</p>

<p><strong><a href="https://tiktokenizer.vercel.app/">Tiktokenizer</a></strong> — 粘贴任意文本，就能看到GPT-4、GPT-3.5和其他模型是怎么把它拆成词元的。</p>

<p>试着输入”httpstatus”看看它怎么切分！然后再试试”h t t p s t a t u s”（带空格），注意它们的区别。</p>

<h2 id="那到底怎么才能让llm正确地反转字母">那到底怎么才能让LLM正确地反转字母？</h2>

<p>既然我们已经理解了问题所在，以下是一些有效的解决方案：</p>

<h3 id="方法一先把字符分开">方法一：先把字符分开</h3>

<p>最简单的办法——给模型它真正能”看到”的字符：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Instead of: "Reverse: httpstatus"
# Do this:
</span><span class="n">prompt</span> <span class="o">=</span> <span class="s">"""The letters are: h, t, t, p, s, t, a, t, u, s
Now write them in reverse order, separated by commas:"""</span>
<span class="c1"># Output: s, u, t, a, t, s, p, t, t, h
# Then join: "sutatsptth" ✓
</span></code></pre></div></div>

<h3 id="方法二思维链chain-of-thought逐步推理">方法二：思维链（Chain-of-Thought，逐步推理）</h3>

<p>让模型先列出所有字母，然后再反转：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">prompt</span> <span class="o">=</span> <span class="s">"""Reverse the letters in "httpstatus".

Step 1: List each letter with its position:
Position 1: h
Position 2: t
Position 3: t
Position 4: p
Position 5: s
Position 6: t
Position 7: a
Position 8: t
Position 9: u
Position 10: s

Step 2: Now list them from last position to first:"""</span>
</code></pre></div></div>

<h3 id="方法三使用代码执行">方法三：使用代码执行</h3>

<p>最可靠的方法——让模型写代码然后运行：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">prompt</span> <span class="o">=</span> <span class="s">"""Write Python code to reverse the string "httpstatus" 
and output only the result."""</span>

<span class="c1"># Model outputs: print("httpstatus"[::-1])
# Result: sutatsptth ✓
</span></code></pre></div></div>

<h3 id="方法四针对字符级任务的微调fine-tuning">方法四：针对字符级任务的微调（Fine-tuning）</h3>

<p>对于生产系统，可以使用<strong>词元内部位置感知（Token Internal Position Awareness, TIPA）</strong> 来微调模型——训练它们完成需要知道词元内字符位置的任务。这是一个活跃的研究方向。</p>

<h2 id="核心要点">核心要点</h2>

<ol>
  <li><strong>LLM看不到单个字母</strong> — 它们看到的是词元（文本块）</li>
  <li><strong>分词（Tokenization）是一种权衡</strong>：效率 vs. 字符级感知能力</li>
  <li><strong>对AI来说，”简单”的文本任务可能很难</strong> — 当这些任务需要字母级别的操作时</li>
  <li><strong>变通方法是有的</strong>：字符分离、思维链和代码执行都能帮上忙</li>
  <li><strong>分词器就是AI的”眼睛”</strong> — 它的能力和局限性决定了模型能做什么</li>
</ol>

<p style="font-size: 1.4em; font-style: italic; text-align: center; margin: 2rem 0;">AI并非误解了语言——它从未以我们的方式看过语言。</p>

<hr />

<h2 id="参考资源">参考资源</h2>

<ul>
  <li><a href="https://tiktokenizer.vercel.app/">Tiktokenizer — Online Tokenizer Visualization Tool</a></li>
  <li><a href="https://www.youtube.com/watch?v=7xTGNNLPyMI">Let’s Build the GPT Tokenizer — Andrej Karpathy (YouTube)</a></li>
  <li><a href="https://huggingface.co/docs/transformers/en/tokenizer_summary">Hugging Face — Tokenization Algorithms Summary</a></li>
  <li><a href="https://huggingface.co/learn/llm-course/en/chapter6/5">Hugging Face — BPE Tokenization Explained</a></li>
  <li><a href="https://github.com/openai/tiktoken">OpenAI Tiktoken Library (GitHub)</a></li>
  <li><a href="https://nebius.com/blog/posts/how-tokenizers-work-in-ai-models">How Tokenizers Work in AI Models (Nebius)</a></li>
  <li><a href="https://sebastianraschka.com/blog/2025/bpe-from-scratch.html">Sebastian Raschka — Implementing BPE from Scratch</a></li>
  <li><a href="https://arxiv.org/html/2412.18626v1">Why LLMs Struggle to Count Letters (arXiv)</a></li>
</ul>]]></content><author><name>Zhipeng Bao</name></author><category term="AI" /><category term="LLM" /><category term="tokenization" /><category term="NLP" /><category term="tutorial" /><summary type="html"><![CDATA[你有没有让ChatGPT把一个单词倒过来拼，结果它给了你一个错误答案？我就遇到过——然后我就掉进了一个关于AI到底是怎么”阅读”文字的兔子洞。]]></summary></entry></feed>