阶段一:基础篇 ⭐ 入门

第 2 章:提示词工程基础语法

用精确的语言驾驭 AI — 构建可复用、可验证的提示词体系

🎯 本章学习目标

  1. 掌握结构化提示词的四要素模板:角色-任务-约束-格式
  2. 理解 Token 预算的工作原理,学会信息分层管理上下文
  3. 能够识别和避免5 种常见反模式
  4. 掌握"肯定优于否定"的提示词表达策略
  5. 建立可复用的个人提示词库

2.1 结构化提示词四要素

每一个职业级的编程提示词,都可以解构为四个核心要素。缺少任何一个,输出质量都会显著下降。

🎭 角色(Role)

定义 AI 的专业身份和视角

不要只说"帮我写代码",而要说"你是一位有 10 年经验的 Linux 内核开发者"。角色设定会改变 AI 对问题的分析深度、用词选择、以及代码风格。

示例: 你是一位精通 Python 和 C 的系统软件工程师,拥有 8 年 Linux 系统编程经验。

📋 任务(Task)

精确描述要完成的工作

使用"做什么 + 为什么做"的结构。不仅描述任务内容,还要说明背景和目的,这能帮助 AI 做出更合理的隐含决策。

示例: 我们需要为 NAS 系统实现磁盘健康监控功能。该功能定期检查所有挂载磁盘的 SMART 状态,在出现预警时发送通知。

🔒 约束(Constraints)

划定边界,收窄 AI 的发挥空间

约束是提示词中最重要也最容易被忽视的部分。约束越精确,输出越可控。包括:技术栈、性能目标、安全要求、编码规范、错误处理策略。

示例: 约束:(1) 使用 Python 3.10+ 标准库;(2) 单文件实现,不超过 300 行;(3) 所有 IO 操作有 5s 超时;(4) 通过 mypy --strict 类型检查。

📤 格式(Format)

指定输出的结构和形式

明确代码结构、注释风格、输出格式(JSON/YAML/纯代码)、文件组织。没有格式指令,AI 可能会把代码、解释、示例混在一起输出。

示例: 输出格式:(1) 一个独立的 Python 文件;(2) 模块级 docstring 说明用途;(3) 每个函数包含类型注解和 docstring;(4) 末尾附使用示例。

2.2 Token 预算与上下文管理

2.2.1 Token 的本质

在 LLM 中,Token 是基本的计价和容量单位。一个中文字约等于 1.5-2 个 token,一个英文单词约等于 1-1.3 个 token,代码 token 消耗通常更高。

上下文窗口的构成(以 200K 窗口为例)

系统提示词 ~20K
对话历史 ~70K
工具结果 ~40K
用户输入 ~50K
AI 输出 ~20K

2.2.2 Token 优化策略

策略说明节省量
精准裁剪只保留与当前任务相关的上下文20-40%
结构化压缩用 YAML/JSON 替代自然语言描述配置15-25%
引用替代重复引用已有代码/文档,不重新描述10-30%
渐进式展开先给概要,确认方向后再展开细节30-50%
CLAUDE.md 预置将项目背景信息放入 CLAUDE.md 自动注入每次节省 2-5K
💡 关键原则:信息密度优先。在有限的 token 预算内,代码片段 > 自然语言描述(更精确),约束清单 > 开放式要求(更可控),示例 > 解释(更直观)。

2.3 "肯定"与"否定"的表达策略

一个有趣的提示词工程规律:AI 对"你应该做什么"的响应远好于"你不应该做什么"。

❌ 否定式(低效)✅ 肯定式(高效)
"不要使用全局变量""所有状态封装在类或函数作用域内"
"不要写太长的函数""每个函数不超过 30 行,单一职责"
"不要忽略错误处理""每个 IO 操作必须有 try-except,记录错误日志"
"不要生成低质量注释""注释说明"为什么",代码说明"做什么""
"不要使用过时的 API""使用 Python 3.10+ 的类型系统和模式匹配"

📋 本章提示词模板

模板 2.1:通用编程任务模板(四要素完整版)

📋
## 角色
你是一位 [专业领域] 工程师,精通 [技术栈],拥有 [X] 年经验。
你的代码风格:[简洁/健壮/高性能],注重 [具体关注点]。

## 任务
[一句话任务描述]

### 背景
[项目上下文、为什么需要这个功能、它与现有系统的关系]

### 功能需求
1. [需求1 - 可验证的描述]
2. [需求2 - 可验证的描述]
3. ...

## 约束
### 技术约束
- 语言/运行时:[Python 3.10+ / C99 / ...]
- 依赖:[仅标准库 / FastAPI + SQLAlchemy / ...]
- 性能:[单次操作 < 100ms / 支持 1000 QPS / ...]

### 质量约束
- 错误处理:[每个 IO 操作有超时 / 异常分类处理 / ...]
- 类型安全:[所有公开函数有类型注解 / mypy strict / ...]
- 安全:[输入验证 / SQL 注入防护 / 路径遍历防护 / ...]

### 设计约束
- [遵循 SOLID 原则 / Repository 模式 / 面向接口编程 / ...]
- [单文件 / 多文件模块 / 包结构]

## 输出格式
- [单一 Python 文件 / 包目录结构 / 带测试文件]
- [包含 docstring / 类型注解 / 使用示例]
- [代码后附简要设计说明]

模板 2.2:代码审查提示词

📋
请对以下代码进行系统性审查,按以下维度逐一检查:

## 安全性(Critical)
- [ ] 是否存在注入风险(SQL/命令/路径遍历)?
- [ ] 敏感信息是否硬编码(密钥/密码/Token)?
- [ ] 输入验证是否完整?

## 正确性(High)
- [ ] 边界条件是否处理(空输入/极大值/并发)?
- [ ] 错误处理是否完整(不吞噬异常)?
- [ ] 资源是否正确释放(文件句柄/连接/锁)?

## 性能(Medium)
- [ ] 是否存在 O(n²) 及以上复杂度的热点?
- [ ] IO 操作是否合理(批量/缓冲/异步)?

## 可维护性(Medium)
- [ ] 命名是否清晰自解释?
- [ ] 函数是否单一职责且足够短?
- [ ] 是否存在重复代码?

请按严重程度排序输出所有发现的问题,每个问题附修复建议。

模板 2.3:错误修复迭代模板

📋
以下是运行 [代码文件] 时遇到的错误。请执行修复闭环:

## 错误信息
```
[粘贴完整错误输出和堆栈跟踪]
```

## 环境信息
- OS: [Linux / macOS / Windows]
- 运行时版本: [Python 3.11 / Node 20 / ...]
- 相关依赖版本: [列表]

## 要求
1. **根因分析**:解释错误的根本原因(不是表象)
2. **修复方案**:提供完整的修复代码(使用 diff 格式标注变更)
3. **预防措施**:建议如何避免此类问题再次发生
4. **回归测试**:生成一个测试用例,确保修复有效且不会复现

🛠️ 实战演练:从反模式到最佳实践

案例:实现一个配置管理器

阶段 1:展示反模式

❌ 反模式:模糊需求 + 一步到位

"写一个配置管理模块,支持读取配置。"

为什么这是反模式:

  • 没说配置格式(JSON? YAML? TOML? INI?)
  • 没说配置来源(文件? 环境变量? 命令行?)
  • 没说如何处理默认值和验证
  • 没说是否需要支持热重载
  • 没说错误处理策略

阶段 2:应用四要素模板

✅ 优化后的提示词
## 角色
你是一位 Python 系统工具库开发者,精通配置管理系统的设计。

## 任务
为 NAS 操作系统项目实现一个配置管理模块 ConfigManager。

### 背景
NAS 系统需要管理多种配置:系统设置、网络配置、用户偏好等。
配置可能来自多个来源,需要统一管理。

### 功能需求
1. 支持 YAML 配置文件读取和写入
2. 支持环境变量覆盖(PREFIX_NAS_ 前缀)
3. 支持嵌套键访问(如 config.get("storage.default_pool"))
4. 支持配置验证(类型检查、范围检查、必填检查)
5. 支持配置变更监听(观察者模式)

## 约束
### 技术约束
- Python 3.10+,仅使用标准库 + PyYAML
- 单文件模块,不超过 200 行核心逻辑

### 质量约束
- 线程安全
- 所有公开方法有类型注解和 docstring
- 配置写入使用原子操作(先写临时文件再 rename)
- 解析失败时给出明确的错误位置和修复建议

### 设计约束
- 遵循单一职责:配置读取、验证、监听分离为独立类
- 接口隔离:定义 ConfigBackend 抽象基类,支持未来扩展

## 输出格式
- 单个 Python 文件 config_manager.py
- 包含完整类型注解
- 包含 3 个以上使用示例
- 末尾附测试用例

阶段 3:AI 输出结果(核心代码片段)

"""NAS 系统配置管理模块 — 支持多源加载、验证、热重载。

使用示例:
    # 基本使用
    config = ConfigManager("config.yaml")
    port = config.get("web.port", default=8080)
    config.set("web.port", 9090)

    # 配置验证
    config.add_validator("web.port", lambda v: 1024 <= v <= 65535)

    # 变更监听
    config.watch("storage.default_pool", lambda k, old, new: print(f"{k}: {old} -> {new}"))
"""

import os
import re
import threading
from abc import ABC, abstractmethod
from collections.abc import Callable
from pathlib import Path
from typing import Any, Optional

import yaml


class ConfigBackend(ABC):
    """配置后端抽象 — 支持扩展到 etcd、Redis 等。"""
    @abstractmethod
    def load(self) -> dict[str, Any]: ...
    @abstractmethod
    def save(self, data: dict[str, Any]) -> None: ...


class YamlFileBackend(ConfigBackend):
    """YAML 文件后端,支持原子写入。"""
    def __init__(self, path: str | Path):
        self.path = Path(path)

    def load(self) -> dict[str, Any]:
        if not self.path.exists():
            return {}
        try:
            with open(self.path, 'r', encoding='utf-8') as f:
                return yaml.safe_load(f) or {}
        except yaml.YAMLError as e:
            raise ConfigError(f"YAML 解析失败 [{self.path}]: {e}") from e

    def save(self, data: dict[str, Any]) -> None:
        tmp_path = self.path.with_suffix('.tmp')
        with open(tmp_path, 'w', encoding='utf-8') as f:
            yaml.safe_dump(data, f, default_flow_style=False, allow_unicode=True)
        tmp_path.rename(self.path)


class ConfigManager:
    """统一配置管理器。"""

    def __init__(self, backend: ConfigBackend | str, env_prefix: str = "NAS_"):
        self._backend = YamlFileBackend(backend) if isinstance(backend, str) else backend
        self._env_prefix = env_prefix
        self._data: dict[str, Any] = {}
        self._validators: dict[str, list[Callable[[Any], None]]] = {}
        self._watchers: dict[str, list[Callable[[str, Any, Any], None]]] = {}
        self._lock = threading.RLock()
        self.reload()

    def reload(self) -> None:
        with self._lock:
            self._data = self._backend.load()
            self._apply_env_overrides()

    def get(self, key: str, default: Any = None) -> Any:
        with self._lock:
            return self._navigate(key, self._data) if self._navigate(key, self._data) is not NotImplemented else default

    def set(self, key: str, value: Any) -> None:
        with self._lock:
            old = self.get(key)
            self._validate(key, value)
            self._set_nested(key, value)
            self._notify(key, old, value)

    def add_validator(self, key: str, validator: Callable[[Any], None]) -> None:
        self._validators.setdefault(key, []).append(validator)

    def watch(self, key: str, callback: Callable[[str, Any, Any], None]) -> None:
        self._watchers.setdefault(key, []).append(callback)

    def save(self) -> None:
        with self._lock:
            self._backend.save(self._data)

    def _navigate(self, key: str, data: dict) -> Any:
        parts = key.split(".")
        current: Any = data
        for part in parts:
            if isinstance(current, dict) and part in current:
                current = current[part]
            else:
                return NotImplemented
        return current

    def _set_nested(self, key: str, value: Any) -> None:
        parts = key.split(".")
        d = self._data
        for part in parts[:-1]:
            d = d.setdefault(part, {})
        d[parts[-1]] = value

    def _apply_env_overrides(self) -> None:
        prefix = f"{self._env_prefix}"
        for env_key, env_val in os.environ.items():
            if env_key.startswith(prefix):
                config_key = env_key[len(prefix):].lower().replace("__", ".")
                try:
                    self._set_nested(config_key, yaml.safe_load(env_val))
                except Exception:
                    self._set_nested(config_key, env_val)

    def _validate(self, key: str, value: Any) -> None:
        for validator in self._validators.get(key, []):
            validator(value)

    def _notify(self, key: str, old: Any, new: Any) -> None:
        for callback in self._watchers.get(key, []):
            try:
                callback(key, old, new)
            except Exception:
                pass  # 监听器异常不应影响配置写入


class ConfigError(Exception):
    """配置错误基类。"""
    pass

阶段 4:审查要点

  • ✅ 检查线程安全:RLock 是否正确覆盖了所有读写操作
  • ✅ 验证原子写入:临时文件 + rename 是否真的保证原子性
  • ✅ 检查环境变量覆盖逻辑:YAML 解析错误是否被正确处理
  • ⚠️ 潜在问题:深层嵌套字典的并发修改可能导致数据竞争
  • ⚠️ 改进空间:可加入配置变更历史(审计日志)

⚠️ 五种典型反模式深度剖析

🔴 反模式 1:模糊需求(Vague Requirements)

症状:"帮我写一个工具" / "实现文件管理功能"
解药:用四要素模板重写,特别是约束和输出格式部分。每个需求必须是可验证的——你能明确判断 AI 是否完成了它。

🔴 反模式 2:一步到位(One-Shot Fallacy)

症状:试图用一个超长提示词生成整个系统
解药:拆分为独立的子任务,先验证架构方向,再逐个实现模块。每个提示词控制在 500-2000 token。

🔴 反模式 3:缺乏验证(No Verification)

症状:AI 生成后直接粘贴使用,不做审查
解药:建立"生成-审查-测试"三道门禁。至少做到:(1) 阅读每一行 (2) 运行基本测试 (3) 检查边界情况。

🔴 反模式 4:上下文污染(Context Pollution)

症状:在提示词中塞入大量无关信息、过时代码、重复内容
解药:使用"渐进式展开"策略。先提供核心上下文(100-200 token),确认 AI 理解正确后再补充细节。

🔴 反模式 5:假精确(False Precision)

症状:"写高质量的代码" / "确保系统安全" / "性能要好"
解药:所有质量要求必须量化或可验证。"高质量" → "通过 pylint 9.0+ 检查";"安全" → "通过 bandit 扫描";"性能好" → "单次查询 < 50ms"。

📝 本章小结

  • 每个职业级提示词包含四要素:角色 → 任务 → 约束 → 格式
  • Token 是稀缺资源,信息密度(代码片段 + 约束清单)优于自然语言描述
  • "肯定优于否定":告诉 AI 要做什么,而不是不要做什么
  • 五种反模式时刻警惕:模糊需求、一步到位、缺乏验证、上下文污染、假精确
  • 好的提示词是可复用资产——建立一个个人提示词库持续积累

🤔 思考练习

  1. 找出你最近 3 次使用 AI 编程时的提示词,按四要素模板重写,对比前后输出质量的差异。
  2. 用模板 2.3 对一段你之前遇到的 bug 进行完整的修复闭环,记录根因分析过程。
  3. 设计一个个人提示词库的存储方案(文件结构、分类方式、检索方法),并存入 3 个经过验证的好用提示词。