📚前言
📒FDE系列内容总纲:
🚄前置课程列表:
见文档结尾附录。
🚀阶段3·Day 55:综合实战 --- 巡检报告生成器与本周收官 🎉
FDE 学习系列教程 · 第三阶段 · 第 7 周 · Day 5(收官) 预计时长:3 小时 | 难度:★★★☆☆ | 前置知识:Day 51-54(API 调用、Prompt 三板斧、结构化输出、推理引导) 对标大纲课时:3.1.1 ~ 3.1.4 综合(本周收官)
📌 一句话目标:把四天所学整合为一个可演示的小产品------喂入巡检数据,自动产出结构化巡检报告;同时建立 Prompt 文件化与版本管理的工程习惯。
🧑🤝🧑 开场:四天的零件,今天组装
先盘一下你这四天攒下的零件:
| Day | 能力 | 产出 |
|---|---|---|
| 51 | 调 API | day51_hello_llm.py:能跟模型说话 |
| 52 | 立规矩 | 工单分类 Prompt(zero-shot / few-shot 两版) |
| 53 | 要 JSON | extract_with_retry():一句话 → 可入库字段 |
| 54 | 引导推理 | CoT 故障分析模板、ReAct 循环雏形 |
但它们现在是四个散落的 .py 文件,Prompt 硬编码在代码字符串里------这在真实项目里活不过两周:
· 改一句 Prompt 要动 Python 代码 → 改完还得重新部署
· 改坏了没法回滚 → 因为旧版被覆盖了
· 想对比两版效果 → 只能靠记忆:"好像上一版好一点?"
所以今天两件事,一件造东西,一件立规矩:
① 造东西:巡检报告生成器
巡检记录(温度/振动)──► 模型 ──► 带异常识别、风险分级、处置建议的 Markdown 报告
② 立规矩:Prompt 文件化 + 版本号
prompts/inspection_report.v1.txt ← 改 Prompt 不用动代码,v1/v2 并存可回滚
做完这个,你的智能工单助手 v0.1 就有两大件了:工单信息提取(Day 53)+ 巡检报告生成(今天)。它们共同长在第二阶段的设备告警工单系统之上:
第二阶段底座 本周新增 v0.1
┌────────────────────┐ ┌──────────────────────────────┐
│ FastAPI 工单接口 │ │ 自然语言报修 → extractor.py → 工单 │
│ MySQL 工单表 │ + │ 巡检记录 → reporter.py → 报告 │
│ 飞书通知 / Webhook │ │ prompts/*.v1.txt(文件化管理) │
└────────────────────┘ └──────────────────────────────┘
📖 一、今天要造的东西:巡检报告生成器
业务场景(车间每天都在发生的事)
巡检员张工早上绕车间一圈,手抄了一堆数:
A3:温度 91.2℃ 振动 2.1
B2:温度 65.0℃ 振动 8.3
C1:温度 82.4℃ 振动 3.0
D5:温度 60.1℃ 振动 2.2
E2:温度(没记) 振动 4.0
然后他得写一份《设备巡检日报》交给车间主任。这份报告最烦的地方不是抄数,而是判断和汇总:哪台异常?多严重?先修哪台?数据不全的要不要编?
这就是我们要让模型干的活:
输入:巡检记录(JSON / 列表)
│
▼
┌──────────────────────────────────────────┐
│ prompts/inspection_report.v1.txt(规则+基准+报告骨架) │
│ + reporter.py(拼装数据、调 llm.chat) │
└──────────────────────────────────────────┘
│
▼
输出:Markdown 报告
# 设备巡检日报(2026-09-21)
## 一、总体概况
## 二、异常设备明细(表格)
## 三、处置建议(按风险排序,带时限)
## 四、备注与需人工确认事项
💡 注意这个形状:数据 → Prompt 模板 → 模型 → 结构化产出 。以后你做的每一个 AI 功能都是这个形状------RAG 只是中间多一步"检索",Agent 只是多几轮循环。今天把它走通一遍,后面的课都是在上面加零件。
为什么这个任务适合模型,不适合 if/else
| 环节 | if/else 能做吗 | 模型能做吗 |
|---|---|---|
| 温度阈值判定 | ✅ 而且更准(规则明确) | ⚠️ 可能算错 |
| 跨指标综合定级 | ❌ 要写一堆组合条件 | ✅ 读规则就行 |
| 生成"处置建议"文字 | ❌ 只能套模板 | ✅ 能结合上下文写得像人写的 |
| 识别"数据缺失/矛盾" | ❌ 要额外写校验 | ✅ 能主动标注 |
| 汇总成可读报告 | ❌ | ✅ |
⚠️ 这里有一个非常重要的工程判断:阈值判定这种"确定性计算",应该让程序做,不该让模型做。 今天让模型顺便算,是为了练手(也因为报告需要综合判断);真上生产,你会先算出等级再喂给模型(见第六节的"工程化升级方向")。别让模型干它不擅长的事------这是 Day 53 就讲过的原则。
📖 二、Prompt 也要"代码化管理"
先把目录结构定下来(今天全部按这个来):
fde-ai/
├── .env # API Key(已在 .gitignore 里)
├── .gitignore
├── day51_hello_llm.py # 前几天的练习脚本,保留做参考
├── day52_zero_shot.py
├── day53_schema.py
├── day54_cot.py
├── day55_report_demo.py # 今天的演示入口
├── outputs/ # 生成的报告落在这里
└── smart_assistant/ # ★ 本周成果归置到这个包
├── __init__.py
├── llm.py # 统一封装的调用函数
├── prompts/
│ ├── extract_ticket.v1.txt
│ └── inspection_report.v1.txt # Prompt 当独立文件管理
├── extractor.py # Day53 的工单提取
└── reporter.py # 今天的报告生成
四条理由,每一条都是踩过坑才有的:
| 理由 | 不这么做的后果 |
|---|---|
| 文件化:Prompt 与代码分离 | 改一句 Prompt 要动代码、重新部署;产品经理没法参与改 Prompt |
| 版本号:v1/v2 并存不覆盖 | 改坏了没法回滚;"上一版明明更好"但找不回来 |
| 一处定义:同类任务共用一份 | 三个地方复制了同一段 Prompt,改了一处漏了两处 |
| 可评测:版本对应实验结果 | 无法回答"v2 到底比 v1 好在哪",优化全凭感觉 |
📌 第 8 周 Langfuse 会把这件事做得更彻底(云端版本管理 + 调用追踪);下周的 Promptfoo 对比也需要"两个版本的文件"作为输入。今天的文件命名约定,就是为下周准备的。
🖥️ 三、实操:搭起 smart_assistant 包
实操步骤 1:统一 LLM 调用封装
smart_assistant/llm.py:
python
"""统一的 LLM 调用入口:所有功能模块共用,方便以后换模型/加追踪"""
import os, json
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
)
def chat(system: str, user: str, temperature: float = 0.7,
max_tokens: int = 1024, json_mode: bool = False) -> str:
kwargs = {}
if json_mode:
kwargs["response_format"] = {"type": "json_object"}
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "system", "content": system},
{"role": "user", "content": user}],
temperature=temperature,
max_tokens=max_tokens,
**kwargs,
)
return resp.choices[0].message.content
为什么要有这么一层"看起来多余"的封装?
① 换模型只改一处:哪天客户要求换私有化模型,改 llm.py 一个文件
② 统一加东西:重试、超时、日志、成本统计、Langfuse 追踪,都加在这一层
③ 业务代码干净:extractor.py / reporter.py 只关心 Prompt 和数据
这就是第二阶段反复强调的分层思想在 AI 代码里的应用。
顺手加一个带用量统计的版本(可选,但对建立成本意识很有用):
python
def chat_with_usage(system: str, user: str, **kwargs) -> tuple[str, dict]:
"""返回 (回答, 用量字典),便于统计成本"""
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "system", "content": system},
{"role": "user", "content": user}],
**kwargs,
)
usage = {
"prompt_tokens": resp.usage.prompt_tokens,
"completion_tokens": resp.usage.completion_tokens,
"total_tokens": resp.usage.total_tokens,
}
return resp.choices[0].message.content, usage
实操步骤 2:写报告生成 Prompt(文件化)
smart_assistant/prompts/inspection_report.v1.txt:
你是制造车间设备巡检分析专家。根据输入的巡检记录生成 Markdown 报告。
【判定基准】
- 温度:正常≤80℃;80~85℃为预警;>85℃为告警
- 振动:正常≤4.5mm/s;4.5~7为预警;>7为告警
- 任一项告警,该设备风险等级为"高";仅预警为"中";全部正常为"低"
【报告结构】严格按以下 Markdown 输出:
# 设备巡检日报({{date}})
## 一、总体概况
- 巡检设备数 / 正常数 / 预警数 / 告警数(各1行)
## 二、异常设备明细
表格列:设备编号 | 温度 | 振动 | 风险等级 | 问题判断
(全部正常时输出"无异常设备")
## 三、处置建议
按风险从高到低,每条含:设备、建议动作、建议时限
## 四、备注与需人工确认事项
列出数据缺失或互相矛盾、需要现场复核之处;没有则写"无"
【规则】
1. 只依据给定数据,禁止虚构未提供的读数
2. 风险等级必须严格按基准计算,不得拍脑袋
3. 涉及告警的设备,建议时限不超过2小时
这个 Prompt 里藏着四天所学的全部要点,值得拆开看:
| 位置 | 用到的知识 | 来自 |
|---|---|---|
| "你是......巡检分析专家" | 角色设定(第一板斧) | Day 52 |
| 【判定基准】三条 | 边界裁决规则、禁止拍脑袋 | Day 52 |
| 【报告结构】小标题 | 结构化骨架、CoT 式分步 | Day 54 |
| "禁止虚构读数" | 防编造约束 | Day 54 |
| 表格列定义 | 输出格式约束(第二道防线) | Day 53 |
📌
{``{date}}是占位符,下一步用replace替换(第 8 周会升级为 Jinja2 模板引擎)。先用最简单的办法把链路跑通,再考虑优雅。
实操步骤 3:把 Day 53 的提取器也搬进包里
smart_assistant/prompts/extract_ticket.v1.txt:
你是工单信息提取器,只输出 JSON 对象,不要输出任何其他文字。
【提取字段】
- title: str,工单标题,4~40字
- device_id: str|null,形如 A3、B12 的设备编号;描述中未明确出现时填 null,禁止猜测
- priority: low|medium|high(涉及安全/停机/泄漏=high;不影响生产=low;其余=medium)
- summary: str,40字以内的一句话摘要
【规则】
1. 只输出 JSON,禁止输出解释或 Markdown 代码块
2. 未提供的信息填 null,严禁编造
3. priority 必须是 low / medium / high 三者之一
smart_assistant/extractor.py:
python
"""自然语言报修 → 结构化工单字段(Day 53 的封装版)"""
import json
from typing import Literal
from pathlib import Path
from pydantic import BaseModel, Field, ValidationError
from .llm import chat
PROMPT_DIR = Path(__file__).parent / "prompts"
class TicketExtract(BaseModel):
title: str = Field(min_length=4, max_length=40)
device_id: str | None = Field(default=None, pattern=r"^[A-Z]+\d+$|^$")
priority: Literal["low", "medium", "high"]
summary: str = Field(max_length=60)
def load_prompt(name: str) -> str:
return (PROMPT_DIR / name).read_text(encoding="utf-8")
def extract_with_retry(text: str, max_retry: int = 3) -> TicketExtract:
system = load_prompt("extract_ticket.v1.txt")
for attempt in range(1, max_retry + 1):
raw = chat(system, text, temperature=0, max_tokens=400, json_mode=True)
try:
return TicketExtract.model_validate_json(raw)
except ValidationError as e:
print(f"[extract] 第 {attempt} 次不合规:{e.errors()}")
text = f"上次输出不符合 schema,错误:{e.errors()},请只输出修正后的 JSON。原文:{text}"
raise RuntimeError(f"提取失败,已重试 {max_retry} 次")
实操步骤 4:报告生成模块 + 演示入口
smart_assistant/reporter.py:
python
"""巡检数据 → Markdown 报告"""
import json
from pathlib import Path
from .llm import chat
PROMPT_DIR = Path(__file__).parent / "prompts"
def load_prompt(name: str) -> str:
return (PROMPT_DIR / name).read_text(encoding="utf-8")
def generate_report(records: list[dict], date: str) -> str:
system = load_prompt("inspection_report.v1.txt").replace("{{date}}", date)
user = "巡检记录 JSON 如下:\n" + json.dumps(records, ensure_ascii=False, indent=2)
return chat(system, user, temperature=0.2, max_tokens=1500)
根目录新建 day55_report_demo.py:
python
"""本周收官演示:巡检记录 → Markdown 报告(打印 + 落盘)"""
from pathlib import Path
from smart_assistant.reporter import generate_report
records = [
{"device_id": "A3", "temperature": 91.2, "vibration": 2.1, "inspector": "张工"},
{"device_id": "B2", "temperature": 65.0, "vibration": 8.3, "inspector": "张工"},
{"device_id": "C1", "temperature": 82.4, "vibration": 3.0, "inspector": "李工"},
{"device_id": "D5", "temperature": 60.1, "vibration": 2.2, "inspector": "李工"},
{"device_id": "E2", "temperature": None, "vibration": 4.0, "inspector": "王工"},
]
if __name__ == "__main__":
report = generate_report(records, date="2026-09-21")
print(report)
Path("outputs").mkdir(exist_ok=True)
Path("outputs/report_2026-09-21.md").write_text(report, encoding="utf-8")
运行:
python day55_report_demo.py
python3 day55_report_demo.py
观察重点(对着输出逐条核对,这就是最朴素的"验收"):
| 检查项 | 期望 | 模型做到了吗 |
|---|---|---|
| A3 温度 91.2 → 告警 → 风险"高" | ✅ | ☐ |
| B2 振动 8.3 → 告警 → 风险"高" | ✅ | ☐ |
| C1 温度 82.4 → 预警(不是告警)→ 风险"中" | ✅ | ☐ |
| D5 全正常 → 风险"低",不进异常表 | ✅ | ☐ |
| E2 温度缺失 → 进"需人工确认",不得编造读数 | ✅ | ☐ |
| 告警设备建议时限 ≤ 2 小时 | ✅ | ☐ |
| 概况里的计数(5 台 / 正常 / 预警 / 告警)对得上 | ✅ | ☐ |
💡 如果某一项没做到,不要急着怪模型 ------先检查 Prompt 里那条规则写清楚没有。这是本周最重要的思维习惯:效果不好 → 先查指令 → 再改指令 → 记录对比。
📊 四、巡检判定基准速查表
这是本次任务的"业务规则",模型和你都要遵守同一套:
| 指标 | 正常 | 预警 | 告警 |
|---|---|---|---|
| 温度 | ≤ 80℃ | 80 ~ 85℃ | > 85℃ |
| 振动 | ≤ 4.5 mm/s | 4.5 ~ 7 | > 7 |
综合定级规则:
任一项告警 → 风险"高"
仅预警 → 风险"中"
全部正常 → 风险"低"
数据缺失 → 不据此定级,列入"需人工确认"
本次 5 台设备的判定答案(用来对答案)
| 设备 | 温度 | 振动 | 温度判定 | 振动判定 | 综合风险 |
|---|---|---|---|---|---|
| A3 | 91.2 | 2.1 | 告警 | 正常 | 高 |
| B2 | 65.0 | 8.3 | 正常 | 告警 | 高 |
| C1 | 82.4 | 3.0 | 预警 | 正常 | 中 |
| D5 | 60.1 | 2.2 | 正常 | 正常 | 低 |
| E2 | 缺失 | 4.0 | 缺失 | 正常 | 待确认 |
处置时限约定:告警设备 ≤ 2 小时;预警设备可安排当班处理。
⚠️ 这些阈值是本课程的教学设定 。真实客户现场,阈值来自设备手册或工艺卡------FDE 的第一件事永远是问清楚"你们的标准是什么",而不是照搬网上或上一家的数字。
🖥️ 五、实操:故意刁难,逼出 Prompt 漏洞
AI 功能不能只测正常数据。边界输入是 Prompt 的照妖镜。
实操步骤 1:准备边界用例
新建 day55_edge_test.py:
python
"""边界用例:用坏数据逼出 Prompt 漏洞"""
from smart_assistant.reporter import generate_report
edge_cases = [
("空记录", []),
("读数全缺", [{"device_id": "A3"}]),
("明显异常值", [{"device_id": "A3", "temperature": 999, "vibration": 0}]),
("设备号缺失", [{"temperature": 90, "vibration": 1.0}]),
("自相矛盾", [{"device_id": "A3", "temperature": 70, "vibration": 1.0, "note": "现场冒烟"}]),
]
for name, records in edge_cases:
print("=" * 60)
print(f"用例:{name} 输入:{records}")
print("-" * 60)
print(generate_report(records, date="2026-09-21"))
跑一遍,把问题记下来。v1 大概率会犯这些错:
| 用例 | v1 常见表现 | 问题严重性 |
|---|---|---|
| 空记录 | 编造出一台设备和一份报告 | 🔴 严重(幻觉) |
| 读数全缺 | 把 A3 判成"低风险/正常" | 🔴 严重(把没数据当成没问题) |
| 温度 999 | 判为"告警"并给出处置建议 | 🟡 中(没识别为异常数据) |
| 设备号缺失 | 报告里出现"未知设备"或干脆漏掉 | 🟡 中 |
| 自相矛盾 | 只按数字判正常,忽略"冒烟" | 🟡 中(漏掉关键文本线索) |
实操步骤 2:改出 v2
不要覆盖 v1------复制一份改名,这就是版本管理:
cp smart_assistant/prompts/inspection_report.v1.txt smart_assistant/prompts/inspection_report.v2.txt
在 v2 的规则部分追加:
4. 巡检记录为空时,除概况写0外,其余各节明确写"今日无巡检数据"
5. 读数超出物理合理范围(温度>200或<0)时,标记为"数据异常待复核",不据此定级
6. 设备读数缺失时,不得判定为"正常",必须列入"需人工确认事项"
7. 记录中的文字备注(如"冒烟""异响")与数值冲突时,优先采信文字描述并标注冲突
然后加一个切换入口(这就是"版本可切换"的最小实现):
python
def generate_report(records: list[dict], date: str, version: str = "v1") -> str:
name = f"inspection_report.{version}.txt"
system = load_prompt(name).replace("{{date}}", date)
user = "巡检记录 JSON 如下:\n" + json.dumps(records, ensure_ascii=False, indent=2)
return chat(system, user, temperature=0.2, max_tokens=1500)
实操步骤 3:v1 vs v2 对比
python
for name, records in edge_cases:
print("=" * 60)
print(f"用例:{name}")
print("--- v1 ---")
print(generate_report(records, date="2026-09-21", version="v1")[:400])
print("--- v2 ---")
print(generate_report(records, date="2026-09-21", version="v2")[:400])
把结果填进记录本(这张表下周会被 Promptfoo 自动化):
| 用例 | v1 表现 | v2 表现 | v2 是否解决 |
|---|---|---|---|
| 空记录 | ☐ | ||
| 读数全缺 | ☐ | ||
| 温度 999 | ☐ | ||
| 设备号缺失 | ☐ | ||
| 自相矛盾 | ☐ |
📌 这就是"评测驱动迭代"的微型实战 :发现问题 → 改 Prompt → 用同一批用例再跑 → 对比记录。第 8 周会把它规模化(30 条用例 + 自动化评分),但流程和今天一模一样。工具变复杂,思路不变。
🖥️ 六、实操:两件套合体,接回工单系统
实操步骤 1:一个入口,两件事
新建 day55_assistant.py------智能工单助手 v0.1 的门面:
python
"""智能工单助手 v0.1:提取工单 + 生成巡检报告"""
from smart_assistant.extractor import extract_with_retry
from smart_assistant.reporter import generate_report
from pathlib import Path
REPAIR_TEXT = "B12冲压机防护门失灵,门没关也能启动,太危险了,赶紧处理"
RECORDS = [
{"device_id": "A3", "temperature": 91.2, "vibration": 2.1, "inspector": "张工"},
{"device_id": "C1", "temperature": 82.4, "vibration": 3.0, "inspector": "李工"},
]
if __name__ == "__main__":
# ① 自然语言报修 → 结构化字段
ticket = extract_with_retry(REPAIR_TEXT)
print("【工单提取】")
print(ticket.model_dump())
# ② 巡检记录 → Markdown 报告
report = generate_report(RECORDS, date="2026-09-21", version="v2")
Path("outputs").mkdir(exist_ok=True)
Path("outputs/report_2026-09-21.md").write_text(report, encoding="utf-8")
print("\n【巡检报告】已生成 outputs/report_2026-09-21.md\n")
print(report[:600])
实操步骤 2:把提取结果送进第二阶段系统
python
import httpx
resp = httpx.post(
"http://localhost:8000/api/tickets",
json=ticket.model_dump(),
headers={"X-API-Key": "fde-secret-2026"}, # 生产请从环境变量读
timeout=10,
)
print(resp.status_code, resp.json())
服务没起来也能验收:把上面换成
print(ticket.model_dump()),确认字段齐、类型对、枚举合法即可。
实操步骤 3:(选做)把报告能力暴露成接口
如果第二阶段那套 FastAPI 还在,加一个接口,让车间主任能随时要报告:
python
from fastapi import FastAPI
from pydantic import BaseModel
from smart_assistant.reporter import generate_report
app = FastAPI(title="智能工单助手 v0.1")
class InspectionRequest(BaseModel):
date: str
records: list[dict]
version: str = "v2"
@app.post("/ai/inspection-report")
def api_inspection_report(req: InspectionRequest):
"""巡检记录 → Markdown 报告"""
return {"date": req.date,
"version": req.version,
"report": generate_report(req.records, req.date, version=req.version)}
测试:
curl -X POST http://localhost:8000/ai/inspection-report \
-H "Content-Type: application/json" \
-d '{"date":"2026-09-21","records":[{"device_id":"A3","temperature":91.2,"vibration":2.1}]}'
到这一步,"AI 能力"和"业务系统"就真正长在一起了。
工程化升级方向(知道就行,后面几周会做)
| 今天的简化 | 生产该怎么做 | 什么时候学 |
|---|---|---|
| 阈值判定交给模型 | 程序先算等级,模型只负责写报告 | 现在就能改(Day 55 练习 3) |
Prompt 里 {``{date}} 用 replace |
Jinja2 模板引擎 | 第 8 周 |
| 没有重试/超时/日志 | llm.py 里加统一处理 | 第 8-10 周 |
| 没有成本/效果追踪 | Langfuse 追踪每次调用 | 第 8 周 |
| 报告没有落库 | 存 MySQL + 生成历史列表 | 自己加 |
📊 七、本周复盘:知识地图与验收清单
本周知识地图
Prompt Engineering 基础(Day 51-55)
│
├─ API 调用(Day 51)
│ ├─ openai SDK + DeepSeek(兼容接口,换 base_url 即可)
│ ├─ messages 三角色:system / user / assistant
│ ├─ 模型无状态:多轮历史自己带
│ ├─ temperature / max_tokens / finish_reason
│ └─ token 计费:输入+输出,输出更贵
│
├─ 三板斧(Day 52)
│ ├─ System 四段式:角色/任务/规则/输出
│ ├─ Zero-shot:常识任务直接下指令
│ ├─ Few-shot:范例对解决边界模糊与格式刁钻
│ ├─ 分隔符隔离外部数据
│ └─ 边界裁决规则(如安全优先)
│
├─ 结构化输出(Day 53)
│ ├─ response_format=json_object
│ ├─ Pydantic:类型/枚举/正则校验
│ ├─ 校验失败回喂错误 → 限次重试
│ └─ 概率文本 → 确定契约的三道防线
│
├─ 推理引导(Day 54)
│ ├─ 快答类 vs 推理类任务分流
│ ├─ CoT:规定分析步骤,先推理后结论
│ ├─ Plan-and-Execute:先计划确认再执行
│ └─ ReAct 雏形:想→做→看循环(Agent 种子)
│
└─ 综合实战(Day 55)
├─ 巡检记录 → 结构化 Markdown 报告
├─ Prompt 文件化 + 版本号
├─ 边界输入驱动 v1→v2 迭代
└─ 智能工单助手 v0.1:提取 + 报告两件套
Prompt 工程化习惯清单
| 习惯 | 做法 | 你做到了吗 |
|---|---|---|
| 文件化 | Prompt 存 prompts/*.txt,代码只负责传数据 |
☐ |
| 版本号 | v1/v2 并存,迭代不覆盖旧版 | ☐ |
| 一处定义 | 同类任务共用一个 system,别到处复制粘贴 | ☐ |
| 变更有记录 | 实验本写清:改了什么、哪条用例变好/变差 | ☐ |
| 边界先行 | 空值、缺失、极端值、恶意输入尽早试 | ☐ |
| 统一入口 | 所有调用过 llm.chat(),方便换模型/加追踪 |
☐ |
本周验收清单(逐条打勾,没过就回去补)
| # | 验收项 | 达标标准 | ☐ |
|---|---|---|---|
| 1 | API 调用 | 不看笔记能独立写出调用脚本并解释 usage | ☐ |
| 2 | 三角色 | 能说清 system 与 user 的分工,能实现多轮 | ☐ |
| 3 | 三板斧 | 同一个分类任务有 zero/few-shot 两版及对比记录 | ☐ |
| 4 | 结构化 | JSON mode + Pydantic + 重试完整跑通 | ☐ |
| 5 | 业务联动 | 提取结果能成功创建第二阶段系统的工单 | ☐ |
| 6 | 推理 | CoT 报告质量明显优于直接提问(有对照) | ☐ |
| 7 | ReAct | 理解循环机制,能解释假工具 demo 的每一步 | ☐ |
| 8 | 工程习惯 | Prompt 文件化、带版本号、有实验记录 | ☐ |
| 9 | v0.1 | 巡检报告生成器可演示,边界用例有处理 | ☐ |
| 10 | 安全 | Key 在 .env 中,.gitignore 配置正确 |
☐ |
📝 本课小结
| 知识点 | 一句话记住 |
|---|---|
| 完整链路 | 数据 → Prompt 模板 → 模型 → 结构化产出 |
| 统一封装 | llm.py 一处调模型,方便换模型/加追踪/加日志 |
| Prompt 文件化 | 改 Prompt 不动代码,产品经理也能改 |
| 版本号 | v1/v2 并存,改坏能回滚,对比能溯源 |
| 判定基准 | 温度 ≤80 正常 / 8085 预警 / >85 告警;振动 ≤4.5 / 4.57 / >7 |
| 综合定级 | 有告警=高,仅预警=中,全正常=低,缺失=待确认 |
| 边界用例 | 空数据、缺读数、异常值、矛盾信息必须测 |
| 迭代闭环 | 发现问题 → 改 Prompt → 同批用例再跑 → 对比记录 |
| 模型分工 | 确定性计算交给程序,模型只做它擅长的判断与表达 |
| v0.1 成果 | 工单提取 + 巡检报告两件套,长在第二阶段系统之上 |
🧠 核心认知 :本周你学会的不是几个"咒语",而是一套把 AI 变成系统能力的方法论 :先调通 API(Day 51),再把任务交代清楚(Day 52),然后让输出变得可被程序消费(Day 53),再让复杂问题被分步推理(Day 54),最后用工程化手段把它们管起来(今天)。Prompt 是会反复迭代的资产,不是一次性的脚本------从今天起,像管代码一样管它。
📋 课后练习
练习 1:把阈值判定从模型手里拿回来(约 40 分钟)
今天的 v2 让模型又判等级又写报告。改进它:在 reporter.py 里先用 Python 算出每台设备的等级,再把带等级的表格喂给模型,让它只负责写"处置建议"和"备注"。
先自己写一遍 judge(),再对照下面的参考实现(判定基准见第四节):
python
def judge(temp, vib) -> str:
"""按判定基准算出风险等级(确定性计算,别交给模型)"""
levels = []
for value, warn, alarm in ((temp, 80, 85), (vib, 4.5, 7)):
if value is None:
levels.append("unknown") # 数据缺失,不据此定级
elif value > alarm:
levels.append("high")
elif value > warn:
levels.append("medium")
else:
levels.append("low")
if "unknown" in levels:
return "待确认"
if "high" in levels:
return "高"
if "medium" in levels:
return "中"
return "低"
验收:
-
用同一批 5 条记录,对比"模型自己判"和"程序判完再喂"两种版本的等级是否一致
-
记录两次调用的
total_tokens差异 -
写一句结论:哪些活从模型手里拿走之后,结果更稳了?
练习 2:把 v2 迭代成 v3(约 40 分钟)
用第五节的 5 个边界用例继续刁难 v2,找出至少 2 个还没解决的问题(例如:设备号缺失时报告里怎么写、同一台设备出现两条记录怎么办、巡检员字段怎么用起来)。
要求:
-
新建
inspection_report.v3.txt(不覆盖 v2),在规则里补上对应条款 -
用完全相同的边界用例跑 v2 和 v3,逐条对比
-
在实验记录本里写:改了哪句、哪条用例从 ❌ 变 ✅、有没有哪条用例反而变差了(这很重要------Prompt 优化经常按下葫芦浮起瓢)
练习 3:给 v0.1 补上"报告归档"(约 30 分钟)
让生成的报告不再只是打印在屏幕上:
-
报告落盘到
outputs/巡检日报_YYYY-MM-DD.md(目录按月份分子目录更好) -
写一个
list_reports()函数,列出outputs/下所有报告文件(用pathlib.Path.glob) -
(进阶)把报告元信息(日期、设备数、告警数、使用的 Prompt 版本)写进一个
outputs/index.json,方便以后检索
这一步看着不 AI,但它是"能演示"和"能用"之间的差别:客户要看的是"上周的报告在哪",不是"再跑一次给我看"。
🔭 下节预告
本周你已经能让模型"干好活",但心里可能总不踏实:
· 换个问法它还稳吗?
· 怎么证明 v2 真的比 v1 好,而不是我运气好?
· 有人在输入框里写"忽略上面的指令"怎么办?
· 线上跑了一个月,到底花了多少钱、哪条 Prompt 用得最多?
下周(第 8 周,Day 56-60)进入 模块 3.1(下):Prompt 评测、安全与管理 ------ 把"凭感觉调 Prompt"升级成"用数据说话":
| Day | 主题 | 你会做出什么 |
|---|---|---|
| 56 | 评测集与指标 | 把本周的批量脚本升级成 ≥30 条标注评测集 |
| 57 | Promptfoo A/B 评测 | 用工具自动跑 v1 vs v2,读懂评测报告 |
| 58 | Prompt 注入与越狱 | 亲手攻击自己的工单助手,再学会防护(分隔符的用武之地) |
| 59 | 模板化与版本管理 | Jinja2 模板 + Prompt 目录规范 |
| 60 | Langfuse 可观测 | 追踪每一次调用:花了多少 token、用了哪个版本、效果如何 |
本周作业请先收好这三样东西 ,下周直接要用:day53_batch_result.json(批量提取结果)、工单分类的 zero/few-shot 两版 Prompt、今天的 inspection_report.v1/v2.txt。
恭喜跑完第三阶段第一周 🎉 休息一下,下周见!
🌍附录:前置课程列表
阶段一:认知启蒙(AI 认知与 FDE 角色)
AI 认知
【FDE系列】阶段1Day 1:AI 层级关系 --- 四个嵌套的圈-CSDN博客
【FDE系列】阶段1Day 2:AI 三阶段发展史 --- 会认 → 会判断 → 会创造-CSDN博客
【FDE系列】阶段1Day 3:符号 AI vs 机器学习 --- 两条路线的本质区别-CSDN博客
【FDE系列】阶段1Day 4:Transformer 的历史意义 --- 2017 年的分水岭-CSDN博客
【FDE系列】阶段1Day 5:本周复习与自测 --- 检验你的 AI 认知地基-CSDN博客
【FDE系列】阶段1Day 6:Transformer 架构 --- 一张图纸盖出千千万万栋楼-CSDN博客
【FDE系列】阶段1Day 7:LLM 本质 --- 文字接龙机器-CSDN博客
【FDE系列】阶段1Day 8:Token --- 模型眼中的最小单位-CSDN博客
【FDE系列】阶段1Day 9:AI 幻觉 --- 为什么会一本正经地胡说八道-CSDN博客
【FDE系列】阶段1Day 10:上下文窗口 --- 模型的记忆力上限 + 本周复习-CSDN博客
【FDE系列】阶段1Day 11:Prompt --- 给模型立规矩-CSDN博客
【FDE系列】阶段1Day 12:Memory --- 让模型记住上下文
【FDE系列】阶段1Day 13:RAG --- 给模型配图书管理员-CSDN博客
【FDE系列】阶段1Day 14:Tool Use --- 让模型动手操作-CSDN博客
【FDE系列】阶段1Day 15:MCP --- 统一的工具接口标准 + 第三周复习-CSDN博客
FDE 基础概念
【FDE系列】阶段1Day 16:什么是 FDE --- 把 AI 变成客户结果的人-CSDN博客
【FDE系列】阶段1Day 17:FDE vs 传统实施 --- 三大本质区别-CSDN博客
【FDE系列】阶段1Day 18:FDE 三重身份 + C6 胜任力模型-CSDN博客
【FDE系列】阶段1Day 19:七阶段行动路径 + 行业经验的价值-CSDN博客
【FDE系列】阶段1Day 20:阶段总结与产出物 --- 第一阶段收官-CSDN博客
阶段二:技术地基(Python + FastAPI + SQL + Docker + API 集成)
Python基础
【FDE系列】阶段2:Day 21:Python 环境搭建 --- 写出你的第一行代码-CSDN博客
【FDE系列】阶段2:Day 22:变量、数据类型、条件判断 --- Python 的"记忆"和"判断"-CSDN博客
【FDE系列】阶段2:Day 23:循环与函数 --- 让代码跑 100 遍、把逻辑打包复用-CSDN博客
【FDE系列】阶段2:Day 24:数据结构 --- 列表、字典、集合、元组-CSDN博客
【FDE系列】阶段2:Day 25:文件读写与 JSON --- 让程序连通外部数据(第一周收官)-CSDN博客
【FDE系列】阶段2:Day 26:模块化编程 --- 把代码拆成"抽屉柜"-CSDN博客
【FDE系列】阶段2:Day 27:异常处理与日志 --- 让程序"摔不烂、查得到"-CSDN博客
FastAPI入门到进阶
【FDE系列】阶段2:Day 28:FastAPI 入门 --- 把你的函数变成 API 服务-CSDN博客
【FDE系列】阶段2:Day 29:FastAPI 进阶 --- Pydantic 模型与完整 CRUD 实战-CSDN博客
【FDE系列】阶段2:Day 30:生产代码规范 --- 测试、类型注解、配置管理(第二周收官)-CSDN博客
SQL基础
【FDE系列】阶段2:Day 31:SQL 基础 --- 增删改查一把梭-CSDN博客
【FDE系列】阶段2:Day 32:多表查询 --- JOIN 与聚合-CSDN博客
【FDE系列】阶段2:Day 33:进阶查询 --- 窗口函数与 CTE-CSDN博客
【FDE系列】阶段2:Day 34:数据清洗 --- 把脏数据捋干净-CSDN博客
【FDE系列】阶段2:Day 35:Python + SQL --- 工单接入 MySQL + 本周收官-CSDN博客
Linux基础
【FDE系列】阶段2:Day 36:Linux 入门与文件操作 --- 扔掉鼠标的第一天-CSDN博客
【FDE系列】阶段2:Day 37:权限、进程与文本三剑客-CSDN博客
【FDE系列】阶段2:Day 38:Shell 脚本 --- 把命令串起来自动跑-CSDN博客
【FDE系列】阶段2:Day 39:Linux 综合实战 --- 让服务无人值守-CSDN博客
【FDE系列】阶段2:Day 40:Shell 进阶 --- 生产级脚本与本周收官-CSDN博客
Docker
【FDE系列】阶段2:Day 41:Docker 入门 --- 把环境装进盒子-CSDN博客
【FDE系列】阶段2:Day 42:Dockerfile 实战 --- 把你的应用打包成镜像-CSDN博客
【FDE系列】阶段2:Day 43:Docker Compose --- 多容器一键编排-CSDN博客
【FDE系列】阶段2:Day 44:Nginx 反向代理 + Git 版本控制-CSDN博客
【FDE系列】阶段2:Day 45:综合实战 --- Docker + Nginx + Git 完整部署与本周收官-CSDN博客
API 集成与系统对接
【FDE系列】阶段2:Day 46:RESTful 设计与认证授权-CSDN博客
【FDE系列】阶段2:Day 47:对接企业系统 --- 飞书 / 钉钉 API-CSDN博客
【FDE系列】阶段2:Day 48:Webhook 处理与数据映射-CSDN博客
【FDE系列】阶段2:Day 49:OpenAPI 文档与接口测试-CSDN博客
【FDE系列】阶段2:Day 50:综合项目 --- 设备告警工单闭环系统 & 第二阶段收官 特殊字符-CSDN博客
阶段三:AI 应用技术(含 SDD 方法论)
AI基础:Prompt Engineering 系统训练
【FDE系列】阶段3:Day 51:从聊天窗口到代码 --- 跟 LLM 的第一次握手-CSDN博客
【FDE系列】阶段3:Day 52:Prompt 三板斧 --- 角色、示例与清晰指令-CSDN博客
【FDE系列】阶段3:Day 53:结构化输出 --- 让模型的回答能进数据库-CSDN博客
【FDE系列】阶段3:Day 54:思维链与推理任务 --- 让模型一步步想清楚-CSDN博客
待完成教程:
RAG 知识检索系统
Agent 框架与开发
Tool Calling 与 MCP
LLM 推理与部署
规范驱动开发与 Agent 工程方法论
阶段四:平台与交付(含 Agent 治理)
阶段五:行业实战与认证