<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Lecture on Hao · AI Agent 学习记录</title><link>https://haokw.github.io/tags/lecture/</link><description>Recent content in Lecture on Hao · AI Agent 学习记录</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Mon, 10 Aug 2026 20:00:00 +0800</lastBuildDate><atom:link href="https://haokw.github.io/tags/lecture/index.xml" rel="self" type="application/rss+xml"/><item><title>第 1 章讲义 · 05 LLM Gateway（统一模型调用服务）</title><link>https://haokw.github.io/posts/2026-08-10-ch1-lecture-05-llm-gateway/</link><pubDate>Mon, 10 Aug 2026 20:00:00 +0800</pubDate><guid>https://haokw.github.io/posts/2026-08-10-ch1-lecture-05-llm-gateway/</guid><description>&lt;blockquote&gt;
&lt;p&gt;配套代码：整个 &lt;code&gt;src/anna/server/&lt;/code&gt; + &lt;code&gt;src/anna/providers/&lt;/code&gt;，入口 &lt;code&gt;main.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;学完本节，你应该能回答：Gateway 解决什么问题？密钥为什么集中在网关？
请求/响应各在哪层校验？模板、错误、用量为什么都要&amp;quot;统一&amp;quot;？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="目录"&gt;目录&lt;a href="#%e7%9b%ae%e5%bd%95" class="post-heading__anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a
 href="#1-fastapi-llm-%e6%9c%8d%e5%8a%a1%e7%bb%9f%e4%b8%80%e5%85%a5%e5%8f%a3%e4%b8%8e%e5%af%86%e9%92%a5%e7%ae%a1%e7%90%86"
 
 
&gt;FastAPI LLM 服务：统一入口与密钥管理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#2-%e6%a8%a1%e5%9e%8b%e8%b0%83%e7%94%a8%e6%8e%a5%e5%8f%a3%e5%b0%81%e8%a3%85-openai-compatible"
 
 
&gt;模型调用接口：封装 OpenAI Compatible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#3-streaming-%e8%be%93%e5%87%ba%e7%bd%91%e5%85%b3%e4%bb%a3%e7%90%86%e9%80%90%e5%9d%97%e8%bd%ac%e5%8f%91"
 
 
&gt;Streaming 输出：网关代理逐块转发&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#4-structured-output%e5%b1%8f%e8%94%bd%e5%ba%95%e5%b1%82%e5%b7%ae%e5%bc%82"
 
 
&gt;Structured Output：屏蔽底层差异&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#5-pydantic-%e6%95%b0%e6%8d%ae%e6%a0%a1%e9%aa%8c%e5%85%a5%e5%8f%a3%e4%b8%8e%e5%87%ba%e5%8f%a3"
 
 
&gt;Pydantic 数据校验：入口与出口&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#6-prompt-%e6%a8%a1%e6%9d%bf%e7%bd%91%e5%85%b3%e7%bb%9f%e4%b8%80%e7%ae%a1%e7%90%86"
 
 
&gt;Prompt 模板：网关统一管理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#7-%e9%94%99%e8%af%af%e5%a4%84%e7%90%86%e4%b8%8e%e9%87%8d%e8%af%95%e9%99%90%e6%b5%81%e8%b6%85%e6%97%b6fallback"
 
 
&gt;错误处理与重试：限流、超时、fallback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#8-token--cost--latency-%e6%97%a5%e5%bf%97"
 
 
&gt;Token / Cost / Latency 日志&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="1-fastapi-llm-服务统一入口与密钥管理"&gt;1. FastAPI LLM 服务：统一入口与密钥管理&lt;a href="#1-fastapi-llm-%e6%9c%8d%e5%8a%a1%e7%bb%9f%e4%b8%80%e5%85%a5%e5%8f%a3%e4%b8%8e%e5%af%86%e9%92%a5%e7%ae%a1%e7%90%86" class="post-heading__anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Gateway 解决的第一件事：各组件不需要各自维护 API 密钥和厂商配置。&lt;/strong&gt;
业务代码只调一个 HTTP 入口，密钥、端点、模型清单全部集中在网关的配置层
（&lt;code&gt;src/anna/config.py&lt;/code&gt;，环境变量驱动）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export DASHSCOPE_API_KEY&lt;span style="color:#f92672"&gt;=&lt;/span&gt;... &lt;span style="color:#75715e"&gt;# 网关统一持有&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export ANNA_BASE_URL&lt;span style="color:#f92672"&gt;=&lt;/span&gt;https://dashscope.aliyuncs.com/compatible-mode/v1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;业务组件永远不接触厂商 SDK 和密钥，只发请求：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; httpx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;resp &lt;span style="color:#f92672"&gt;=&lt;/span&gt; httpx&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://127.0.0.1:8000/v1/chat/completions&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; json&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;model&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;auto&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;messages&amp;#34;&lt;/span&gt;: [{&lt;span style="color:#e6db74"&gt;&amp;#34;role&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;user&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;hi&amp;#34;&lt;/span&gt;}]},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这带来三个收益：密钥泄露面缩小（只有网关持有）、厂商切换对业务无感、
调用行为（限流/用量/错误）全部可观测。入口是 FastAPI 应用（&lt;code&gt;main.py&lt;/code&gt; +
&lt;code&gt;create_app&lt;/code&gt;），&lt;code&gt;/docs&lt;/code&gt; 自带 Swagger 调试。&lt;/p&gt;</description></item><item><title>第 1 章讲义 · 04 Structured Output 结构化输出</title><link>https://haokw.github.io/posts/2026-08-10-ch1-lecture-04-structured-output/</link><pubDate>Mon, 10 Aug 2026 19:50:00 +0800</pubDate><guid>https://haokw.github.io/posts/2026-08-10-ch1-lecture-04-structured-output/</guid><description>&lt;blockquote&gt;
&lt;p&gt;配套代码：结构化引擎 &lt;code&gt;src/anna/structured.py&lt;/code&gt;、网关端点 &lt;code&gt;/v1/structured&lt;/code&gt;
（&lt;code&gt;src/anna/server/app.py&lt;/code&gt;）、三家 provider 的原生结构化输出实现、
首页&amp;quot;结构化输出&amp;quot;面板&lt;/p&gt;
&lt;p&gt;学完本节，你应该能回答：为什么 Agent 不能靠自然语言解析？JSON Schema 怎么
定义？Pydantic 和 Schema 是什么关系？模型原生结构化输出怎么用？校验失败
怎么纠错、怎么降级？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="目录"&gt;目录&lt;a href="#%e7%9b%ae%e5%bd%95" class="post-heading__anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a
 href="#1-%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%8d%e8%83%bd%e4%be%9d%e8%b5%96%e8%87%aa%e7%84%b6%e8%af%ad%e8%a8%80%e8%a7%a3%e6%9e%90"
 
 
&gt;为什么不能依赖自然语言解析&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#2-json-schema%e5%ae%9a%e4%b9%89%e8%be%93%e5%87%ba%e7%bb%93%e6%9e%84"
 
 
&gt;JSON Schema：定义输出结构&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#3-pydantic-model%e5%ae%9a%e4%b9%89--%e6%a0%a1%e9%aa%8c--%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96"
 
 
&gt;Pydantic Model：定义 + 校验 + 反序列化&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#4-%e6%a8%a1%e5%9e%8b%e5%8e%9f%e7%94%9f%e7%bb%93%e6%9e%84%e5%8c%96%e8%be%93%e5%87%ba"
 
 
&gt;模型原生结构化输出&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#5-schema-%e6%a0%a1%e9%aa%8c"
 
 
&gt;Schema 校验&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#6-%e8%be%93%e5%87%ba%e7%ba%a0%e9%94%99%e4%b8%8e%e9%87%8d%e8%af%95"
 
 
&gt;输出纠错与重试&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#7-%e7%bb%93%e6%9e%84%e5%8c%96%e8%be%93%e5%87%ba%e5%a4%b1%e8%b4%a5%e5%a4%84%e7%90%86%e9%99%8d%e7%ba%a7%e4%b8%8e%e5%9b%9e%e9%80%80"
 
 
&gt;结构化输出失败处理：降级与回退&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#8-%e8%be%93%e5%87%ba%e6%a0%bc%e5%bc%8f%e4%b8%8e%e4%b8%9a%e5%8a%a1%e5%8d%8f%e8%ae%ae%e8%ae%be%e8%ae%a1"
 
 
&gt;输出格式与业务协议设计&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="1-为什么不能依赖自然语言解析"&gt;1. 为什么不能依赖自然语言解析&lt;a href="#1-%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%8d%e8%83%bd%e4%be%9d%e8%b5%96%e8%87%aa%e7%84%b6%e8%af%ad%e8%a8%80%e8%a7%a3%e6%9e%90" class="post-heading__anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Agent 系统里，模型输出要喂给&lt;strong&gt;代码&lt;/strong&gt;：查词结果要填进卡片、工具参数要发请求、
SQL 要执行。自然语言解析的典型翻车：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;模型回复：&amp;#34;好的，单词是 hello，意思是 你好，例句是 Hello world！&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;正则/切片解析这种输出，任何换行、加粗、多余解释都会破坏结果；更糟的是
解析失败时&lt;strong&gt;你不知道它是格式错了还是答案错了&lt;/strong&gt;。所以工程原则是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;输出协议先行&lt;/strong&gt;：先定义&amp;quot;模型必须返回什么形状&amp;quot;，再让它生成；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;校验兜底&lt;/strong&gt;：返回后必须校验，而不是信任；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;失败有出路&lt;/strong&gt;：校验不过就纠错重试或降级，绝不把脏数据传给下游。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="2-json-schema定义输出结构"&gt;2. JSON Schema：定义输出结构&lt;a href="#2-json-schema%e5%ae%9a%e4%b9%89%e8%be%93%e5%87%ba%e7%bb%93%e6%9e%84" class="post-heading__anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;JSON Schema 是描述 JSON 结构的标准：字段名、类型、是否必填、取值范围。它是
&lt;strong&gt;跨语言的协议语言&lt;/strong&gt;——模型、Python、前端都能理解。&lt;/p&gt;</description></item><item><title>第 1 章讲义 · 03 Prompt Engineering</title><link>https://haokw.github.io/posts/2026-08-10-ch1-lecture-03-prompt-engineering/</link><pubDate>Mon, 10 Aug 2026 19:40:00 +0800</pubDate><guid>https://haokw.github.io/posts/2026-08-10-ch1-lecture-03-prompt-engineering/</guid><description>&lt;blockquote&gt;
&lt;p&gt;配套代码：模板库 &lt;code&gt;src/anna/prompts.py&lt;/code&gt;、种子模板 &lt;code&gt;prompts.json&lt;/code&gt;、
模板 API &lt;code&gt;src/anna/server/app.py&lt;/code&gt;、首页模板管理与注入演示
（&lt;code&gt;src/anna/server/static/index.html&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;学完本节，你应该能回答：System Prompt 该写什么？怎么让输出风格可控？
Few-shot 什么时候有效？复杂任务为什么要拆步？模板怎么做到可复用可版本化？
用户输入怎么注入指令、怎么防？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="目录"&gt;目录&lt;a href="#%e7%9b%ae%e5%bd%95" class="post-heading__anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a
 href="#1-system-prompt-%e7%9a%84%e8%81%8c%e8%b4%a3"
 
 
&gt;System Prompt 的职责&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#2-%e8%a7%92%e8%89%b2%e5%ae%9a%e4%b9%89%e4%b8%8e%e8%a1%8c%e4%b8%ba%e7%ba%a6%e6%9d%9f"
 
 
&gt;角色定义与行为约束&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#3-%e8%be%93%e5%87%ba%e9%a3%8e%e6%a0%bc%e6%8e%a7%e5%88%b6"
 
 
&gt;输出风格控制&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#4-few-shot-%e7%a4%ba%e4%be%8b"
 
 
&gt;Few-shot 示例&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#5-%e4%bb%bb%e5%8a%a1%e6%8b%86%e8%a7%a3"
 
 
&gt;任务拆解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#6-prompt-%e6%a8%a1%e6%9d%bf"
 
 
&gt;Prompt 模板&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#7-prompt-%e6%b3%a8%e5%85%a5%e9%a3%8e%e9%99%a9%e4%b8%8e%e9%98%b2%e6%8a%a4"
 
 
&gt;Prompt 注入风险与防护&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#8-prompt-%e7%89%88%e6%9c%ac%e7%ae%a1%e7%90%86"
 
 
&gt;Prompt 版本管理&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="1-system-prompt-的职责"&gt;1. System Prompt 的职责&lt;a href="#1-system-prompt-%e7%9a%84%e8%81%8c%e8%b4%a3" class="post-heading__anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;System Prompt 相当于模型的&lt;strong&gt;岗位说明书&lt;/strong&gt;：它不回答具体问题，而是定义&amp;quot;你是谁、
要完成什么、边界是什么&amp;quot;。它的四个作用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设定全局行为基调（语气、语言、价值观约束）；&lt;/li&gt;
&lt;li&gt;声明任务目标（这个系统是干嘛的）；&lt;/li&gt;
&lt;li&gt;定义输入输出约定（收到什么、返回什么格式）；&lt;/li&gt;
&lt;li&gt;声明不可越界的规则（不执行用户内嵌指令、不泄露设定）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本仓库的种子模板 &lt;code&gt;reading_assistant&lt;/code&gt; 就是一个 System Prompt 实例（&lt;code&gt;prompts.json&lt;/code&gt;）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;你是 Anna，一位耐心、爱读书、会催你复习的英语陪练。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;当前学习者水平：{user_level}。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;回答使用英语为主、中文解释为辅，遇到新词主动给出释义与例句。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;{user_level}&lt;/code&gt; 是可变参数——同一个岗位说明书，可以按学习者水平渲染不同版本：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; anna.prompts &lt;span style="color:#f92672"&gt;import&lt;/span&gt; PromptStore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;store &lt;span style="color:#f92672"&gt;=&lt;/span&gt; PromptStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;load()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;system &lt;span style="color:#f92672"&gt;=&lt;/span&gt; store&lt;span style="color:#f92672"&gt;.&lt;/span&gt;render(&lt;span style="color:#e6db74"&gt;&amp;#34;reading_assistant&amp;#34;&lt;/span&gt;, user_level&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;初级&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# =&amp;gt; &amp;#34;你是 Anna，一位耐心、爱读书、会催你复习的英语陪练。\n当前学习者水平：初级。...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;关键认知&lt;/strong&gt;：System Prompt 和用户消息是两个独立通道（&lt;code&gt;role: system&lt;/code&gt; vs
&lt;code&gt;role: user&lt;/code&gt;），模型通常给 system 更高权重。这也是为什么第 7 节&amp;quot;注入防护&amp;quot;
要从 system 层做，而不是在用户消息里补救。&lt;/p&gt;</description></item><item><title>第 1 章讲义 · 02 Streaming 流式输出</title><link>https://haokw.github.io/posts/2026-08-10-ch1-lecture-02-streaming/</link><pubDate>Mon, 10 Aug 2026 19:30:00 +0800</pubDate><guid>https://haokw.github.io/posts/2026-08-10-ch1-lecture-02-streaming/</guid><description>&lt;blockquote&gt;
&lt;p&gt;配套代码：网关的流式端点（&lt;code&gt;src/anna/server/app.py&lt;/code&gt;）、SSE 组装与解析
（&lt;code&gt;src/anna/server/openai_format.py&lt;/code&gt;、&lt;code&gt;src/anna/providers/base.py&lt;/code&gt;）、
首页可视化测试台（&lt;code&gt;src/anna/server/static/index.html&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;学完本节，你应该能回答：为什么要把一次 LLM 调用切成一块一块返回？
SSE 协议长什么样？FastAPI 怎么保持低延迟转发？前端怎么逐块消费？
用户点&amp;quot;停止&amp;quot;会发生什么？流到一半断了怎么处理？长文本怎么续写？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="目录"&gt;目录&lt;a href="#%e7%9b%ae%e5%bd%95" class="post-heading__anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a
 href="#1-%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81-streaming"
 
 
&gt;为什么需要 Streaming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#2-server-sent-eventssse"
 
 
&gt;Server-Sent Events（SSE）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#3-fastapi-%e6%b5%81%e5%bc%8f%e6%8e%a5%e5%8f%a3streamingresponse"
 
 
&gt;FastAPI 流式接口：StreamingResponse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#4-%e5%89%8d%e7%ab%af--cli-%e6%b6%88%e8%b4%b9%e6%b5%81%e5%bc%8f%e8%be%93%e5%87%ba"
 
 
&gt;前端 / CLI 消费流式输出&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#5-%e4%b8%ad%e6%96%ad%e4%b8%8e%e5%8f%96%e6%b6%88"
 
 
&gt;中断与取消&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#6-%e6%b5%81%e5%bc%8f%e8%be%93%e5%87%ba%e5%bc%82%e5%b8%b8%e5%a4%84%e7%90%86"
 
 
&gt;流式输出异常处理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#7-%e9%95%bf%e6%96%87%e6%9c%ac%e8%be%93%e5%87%ba%e7%9a%84%e7%8a%b6%e6%80%81%e4%bf%9d%e5%ad%98"
 
 
&gt;长文本输出的状态保存&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="1-为什么需要-streaming"&gt;1. 为什么需要 Streaming&lt;a href="#1-%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81-streaming" class="post-heading__anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;LLM 推理不是&amp;quot;秒回&amp;quot;：一次回答要先经过提示词处理、逐 token 自回归生成，短则几秒、
长则几十秒。&lt;strong&gt;非流式 = 客户端一直等到全部生成完才拿到第一个字&lt;/strong&gt;，用户看到的是
一片空白；流式 = 模型每吐出一个 token 就立刻送出去，用户看到的是&amp;quot;边生成边打字&amp;quot;。&lt;/p&gt;
&lt;p&gt;衡量这个差距有两个指标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;首字节耗时（time to first byte, TTFB）&lt;/strong&gt;：从发出请求到收到第一块内容；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;总耗时&lt;/strong&gt;：从发出请求到收到最后一块内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;非流式请求的 TTFB ≈ 总耗时（必须等完整结果）；流式请求的 TTFB 可以小到
&amp;ldquo;模型生成第一个 token 的时间&amp;rdquo;，总耗时不变，但感知延迟大幅下降。&lt;/p&gt;
&lt;p&gt;本仓库首页的&amp;quot;流式 vs 非流式&amp;quot;对比面板实测过一组数据：&lt;/p&gt;</description></item><item><title>第 1 章讲义 · 01 LLM API 与模型调用基础</title><link>https://haokw.github.io/posts/2026-08-10-ch1-lecture-01-llm-api-basics/</link><pubDate>Mon, 10 Aug 2026 19:20:00 +0800</pubDate><guid>https://haokw.github.io/posts/2026-08-10-ch1-lecture-01-llm-api-basics/</guid><description>&lt;blockquote&gt;
&lt;p&gt;配套代码：本仓库的 provider 层（&lt;code&gt;src/anna/providers/&lt;/code&gt;）与 LLM 统一模型调用服务
（&lt;code&gt;src/anna/server/&lt;/code&gt;、&lt;code&gt;main.py&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;学完本节，你应该能回答：一次 LLM 调用请求长什么样？参数怎么影响输出？
token 怎么算？怎么让模型稳定返回 JSON？出错怎么办？多个模型怎么统一调？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="目录"&gt;目录&lt;a href="#%e7%9b%ae%e5%bd%95" class="post-heading__anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a
 href="#1-chat-completions--responses-api"
 
 
&gt;Chat Completions / Responses API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#2-%e6%a8%a1%e5%9e%8b%e5%8f%82%e6%95%b0temperaturetoppmax_tokens"
 
 
&gt;模型参数：temperature、top_p、max_tokens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#3-token-%e4%b8%8e-context-window"
 
 
&gt;Token 与 Context Window&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#4-structured-output"
 
 
&gt;Structured Output&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#5-%e9%94%99%e8%af%af%e7%b1%bb%e5%9e%8b%e4%b8%8e%e5%bc%82%e5%b8%b8%e5%a4%84%e7%90%86"
 
 
&gt;错误类型与异常处理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
 href="#6-%e5%a4%9a%e6%a8%a1%e5%9e%8b%e8%b0%83%e7%94%a8%e5%9f%ba%e7%a1%80"
 
 
&gt;多模型调用基础&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="1-chat-completions--responses-api"&gt;1. Chat Completions / Responses API&lt;a href="#1-chat-completions--responses-api" class="post-heading__anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id="11-请求结构以-openai-chat-completions-为标准"&gt;1.1 请求结构：以 OpenAI Chat Completions 为标准&lt;a href="#11-%e8%af%b7%e6%b1%82%e7%bb%93%e6%9e%84%e4%bb%a5-openai-chat-completions-%e4%b8%ba%e6%a0%87%e5%87%86" class="post-heading__anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;主流模型 API 基本都是&amp;quot;HTTP POST + JSON 请求体&amp;quot;，OpenAI 的
&lt;code&gt;POST /v1/chat/completions&lt;/code&gt; 是事实标准。先看完整请求：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -s https://api.openai.com/v1/chat/completions &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Authorization: Bearer &lt;/span&gt;$OPENAI_API_KEY&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#39;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;model&amp;#34;: &amp;#34;gpt-4o-mini&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;messages&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; {&amp;#34;role&amp;#34;: &amp;#34;system&amp;#34;, &amp;#34;content&amp;#34;: &amp;#34;你是英语陪练 Anna。&amp;#34;},
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; {&amp;#34;role&amp;#34;: &amp;#34;user&amp;#34;, &amp;#34;content&amp;#34;: &amp;#34;什么是 token？&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;temperature&amp;#34;: 0.7,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;max_tokens&amp;#34;: 200,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;stream&amp;#34;: false
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; }&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;请求体字段（重要程度递减）：&lt;/p&gt;</description></item></channel></rss>