第 1 节 · 免费文字实战

简历生成器项目

从一个脱敏 JSON 开始,用 Python 完成读取、校验、Markdown 简历生成、测试与交付;再选择是否接入 AI API。

免费公开无需登录核心约 90 分钟 · API 加餐约 20 分钟
本节目录
  1. 这一节会完成什么
  2. 开始之前:先定义完成
  3. Python基础环境搭建
  4. 先探索,再让 AI 给计划
  5. 简历模板设计
  6. 数据处理和格式化
  7. AI API调用入门
  8. 测试、Debug 与 Final Review
  9. 项目打包和分享
  10. 实践项目: 个人简历自动生成器
  11. 当前限制与完成标准

这一节不是让 AI 一次吐出一大段代码,而是带你走完一轮可复现的 Vibe Coding:先说清目标和边界,再分两轮实现、验证、调试和交付。最后你会得到一个能把结构化个人信息转换成 Markdown 简历的 Python 项目。

第 1 节 · 免费文字实战
核心路径约 90 分钟;AI API 加餐约 20 分钟。没有 API Key 也可以完成全部必做任务。

这一节会完成什么

课程内容清单:

  1. Python基础环境搭建
  2. AI API调用入门
  3. 简历模板设计
  4. 数据处理和格式化
  5. 项目打包和分享
  6. 实践项目: 个人简历自动生成器

最终的数据流是:

脱敏的 resume_input.json
        ↓
Python 读取、校验和格式化
        ↓
离线模板 ─────────────→ resume.md(必做)
        ↓
结构化 Prompt ────────→ resume_prompt.txt(必做)
        ↓
OpenAI Responses API ─→ resume_ai.md(可选)

完成不等于“程序没有报错”。你需要同时交出:生成文件、测试结果、一次失败修复记录、README 和独立迁移挑战。

开始之前:先定义完成

Goal

做一个命令行简历生成器:读取 JSON 中的真实经历,输出结构清楚的 Markdown 简历。

Context

  • 学习者刚开始接触 Python;
  • 输入来自一个 UTF-8 编码的 JSON 文件;
  • 输出先使用确定性的离线模板;
  • AI API 只作为可选增强,不影响核心项目完成;
  • 示例数据必须脱敏,不能把身份证号、住址、电话或真实密钥放进项目。

Constraints

  • 不使用 Web 框架、数据库、登录和部署;
  • 不让模型虚构经历、数字、技能或求职结果;
  • API Key 只从环境变量读取;
  • 每次只修改一个小目标,修改后立即验证;
  • 参考结果只能用于完成后的复核,不能代替自己的实现过程。

Acceptance

项目完成时必须满足:

  • python app.py --mode template 能生成 resume.md
  • python app.py --mode prompt 能生成 resume_prompt.txt
  • 缺少必填字段时,程序给出能定位问题的错误;
  • 输出中的事实都能在输入 JSON 中找到;
  • 没有 API Key 时,离线模式仍可正常完成;
  • 项目包不包含 Key、虚拟环境、缓存或真实隐私数据;
  • 在一个新对话中独立增加一个字段,并重新写出验收标准。

先把上面四段复制到你的 WORKLOG.md。它们是你与 AI 编码工具的工作合同。

Python基础环境搭建

本课建议使用 Python 3.10 或更高版本。先打开终端,不要急着安装一堆框架。

1. 检查 Python

macOS 或 Linux:

python3 --version

Windows PowerShell:

python --version

看到 Python 3.103.113.12 或更高版本即可继续。若 python 找不到而 python3 可用,后续命令统一把 python 换成 python3

2. 建立独立项目目录和虚拟环境

macOS 或 Linux:

mkdir vibe-resume-generator
cd vibe-resume-generator
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip

Windows PowerShell:

mkdir vibe-resume-generator
cd vibe-resume-generator
python -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip

激活后,终端行首通常会出现 (.venv)。再运行一次:

python --version
python -m pip --version

把两行真实输出记进 WORKLOG.md。不要只写“环境完成”。

3. 建立最小文件结构

vibe-resume-generator/
├── app.py
├── resume_input.json
├── tests/
│   └── test_app.py
├── README.md
├── WORKLOG.md
├── ACCEPTANCE.md
└── .gitignore

.gitignore 先写这些内容:

.venv/
__pycache__/
*.pyc
.env
resume.md
resume_ai.md
resume_prompt.txt

先探索,再让 AI 给计划

在 AI 编码工具里开一个新对话,提供 Goal / Context / Constraints / Acceptance,然后只授权只读探索:

先不要修改文件。请检查当前目录和规格,回答:
1. 现在有哪些文件;
2. 为完成最小离线简历生成器还缺什么;
3. 给出不超过 3 步的实现计划;
4. 每一步准备如何验证。

审计划时删掉不属于本节的内容。若计划出现网页界面、数据库、账号系统、PDF 排版或自动投递,直接要求收窄。你需要的是一条短而可验证的主线:

读取与校验 → 模板与保存 → 测试与交付

这一步训练的是“审计划后授权”,不是把决定权交给 AI。

简历模板设计

1. 把 JSON 当作唯一事实源

resume_input.json 中写入脱敏示例:

{
  "name": "小林",
  "target_role": "数据分析实习生",
  "education": "某大学 信息管理专业",
  "skills": ["Python", "Pandas", "SQL"],
  "projects": [
    {
      "name": "订单数据分析",
      "description": "清洗脱敏订单数据,分析销售趋势并输出图表"
    }
  ],
  "experience": [
    "使用 Python 整理课程数据",
    "为分析结果编写 Markdown 说明"
  ]
}

输入合同包含 6 个必填字段:

字段类型用途
name字符串简历标题
target_role字符串目标岗位
education字符串教育背景
skills字符串列表技能清单
projects对象列表项目名称与描述
experience字符串列表经历亮点

2. 先定输出合同

resume.md 必须按固定顺序包含:

# 姓名

目标岗位:岗位名称

## 教育背景
## 技能
## 项目经历
## 经历亮点

离线模板的价值是结果稳定、可测试、无网络费用。AI 输出可以优化表达,但不能替代事实合同。

3. 给 Prompt 加事实边界

后续 API 模式使用的 Prompt 至少包含:角色、任务、输入、输出格式和禁止事项。最重要的一句是:

只能使用输入中明确提供的事实;不要虚构经历、数字、技能或成果。

这句是约束,不是保证。任何模型输出都还要人工逐条核对。

数据处理和格式化

现在进入第一轮 Build → Verify:只实现读取与校验,不急着接 API。

Build 1:读取 JSON 并尽早失败

app.py 中先写:

Python 示例 · 只读
import json
from pathlib import Path
from typing import Any

REQUIRED_FIELDS = {
    "name": str,
    "target_role": str,
    "education": str,
    "skills": list,
    "projects": list,
    "experience": list,
}


def load_profile(path: Path) -> dict[str, Any]:
    with path.open("r", encoding="utf-8") as file:
        profile = json.load(file)

    if not isinstance(profile, dict):
        raise ValueError("简历输入必须是一个 JSON 对象。")

    for field, expected_type in REQUIRED_FIELDS.items():
        if field not in profile:
            raise ValueError(f"缺少必填字段:{field}")
        if not isinstance(profile[field], expected_type):
            raise ValueError(f"字段 {field} 应为 {expected_type.__name__}。")

    if not all(isinstance(item, str) and item.strip() for item in profile["skills"]):
        raise ValueError("skills 中的每一项都必须是非空字符串。")

    for index, project in enumerate(profile["projects"], start=1):
        if not isinstance(project, dict):
            raise ValueError(f"第 {index} 个项目必须是 JSON 对象。")
        if not isinstance(project.get("name"), str) or not project["name"].strip():
            raise ValueError(f"第 {index} 个项目缺少字符串字段 name。")
        if not isinstance(project.get("description"), str) or not project["description"].strip():
            raise ValueError(f"第 {index} 个项目缺少字符串字段 description。")

    return profile

Verify 1:先看成功,再制造一个失败

临时在文件末尾加:

Python 示例 · 只读
profile = load_profile(Path("resume_input.json"))
print(profile["name"], profile["target_role"])

运行:

python app.py

然后复制一份 JSON,删掉 target_role 再运行。你应该看到包含 缺少必填字段:target_role 的错误。把命令、错误和原因记录到 WORKLOG.md,再恢复正确输入。

如果看到 JSONDecodeError,先检查上一行末尾的逗号、英文双引号和大括号是否成对。先修输入,不要用宽泛的 try/except 把错误吞掉。

Build 2:格式化 Markdown

继续在 app.py 中增加:

Python 示例 · 只读
def render_template_resume(profile: dict[str, Any]) -> str:
    lines = [
        f"# {profile['name'].strip()}",
        "",
        f"目标岗位:{profile['target_role'].strip()}",
        "",
        "## 教育背景",
        "",
        profile["education"].strip(),
        "",
        "## 技能",
        "",
    ]
    lines.extend(f"- {skill.strip()}" for skill in profile["skills"])
    lines.extend(["", "## 项目经历", ""])

    for project in profile["projects"]:
        lines.extend([
            f"### {project['name'].strip()}",
            "",
            f"- {project['description'].strip()}",
            "",
        ])

    lines.extend(["## 经历亮点", ""])
    lines.extend(f"- {item.strip()}" for item in profile["experience"])
    return "\n".join(lines).rstrip() + "\n"

再增加一个稳定的 Prompt 生成函数:

Python 示例 · 只读
def build_prompt(profile: dict[str, Any]) -> str:
    profile_json = json.dumps(profile, ensure_ascii=False, indent=2)
    return f"""你是一名求职简历顾问。
请根据下面的个人信息生成 Markdown 中文简历。

要求:
1. 结构清楚,目标岗位明确;
2. 只能使用输入中明确提供的事实;
3. 不要虚构经历、数字、技能或成果;
4. 信息不足时保守表达;
5. 只输出 Markdown 正文。

个人信息:
{profile_json}
"""

最后把临时的两行运行代码替换成命令行入口:

Python 示例 · 只读
import argparse


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="生成 Markdown 简历。")
    parser.add_argument("--input", type=Path, default=Path("resume_input.json"))
    parser.add_argument("--output", type=Path, default=Path("resume.md"))
    parser.add_argument("--mode", choices=("template", "prompt", "openai"), default="template")
    parser.add_argument("--prompt-output", type=Path, default=Path("resume_prompt.txt"))
    return parser.parse_args()


def main() -> None:
    args = parse_args()
    profile = load_profile(args.input)

    if args.mode == "prompt":
        args.prompt_output.write_text(build_prompt(profile), encoding="utf-8")
        print(f"已生成 Prompt:{args.prompt_output}")
        return

    content = render_template_resume(profile)
    args.output.write_text(content, encoding="utf-8")
    print(f"已生成简历:{args.output}")


if __name__ == "__main__":
    main()

Verify 2:运行两条真实命令

python app.py --mode template
python app.py --mode prompt

检查:

  • resume.md 能正常显示中文;
  • 姓名、岗位、教育、技能、项目和经历都出现;
  • resume_prompt.txt 中的 JSON 没有变成 \u4e00 这类转义;
  • 输出没有出现输入中不存在的数字或成果。

AI API调用入门

这一段是可选进阶。API 调用需要你自己的 OpenAI API Key、联网环境和可用额度,并可能产生费用。网站不会索取、代存或替你运行 Key。

1. 安装官方 Python SDK

保持虚拟环境已激活:

python -m pip install openai

2. 只在环境变量中设置 Key

macOS 或 Linux 当前终端:

export OPENAI_API_KEY="<仅在本机粘贴>"

Windows PowerShell 当前窗口:

$Env:OPENAI_API_KEY="<仅在本机粘贴>"

不要把 Key 写入 app.py、JSON、.env.example、README、截图或 Git 提交。终端共享屏幕前也要确认历史记录里没有敏感值。

3. 使用 Responses API

app.py 顶部增加 import os,再增加:

Python 示例 · 只读
def generate_with_openai(prompt: str, model: str) -> str:
    if not os.getenv("OPENAI_API_KEY"):
        raise RuntimeError("未设置 OPENAI_API_KEY;请先使用 template 或 prompt 模式。")

    try:
        from openai import OpenAI
    except ImportError as error:
        raise RuntimeError("未安装 openai;请先运行 python -m pip install openai。") from error

    client = OpenAI()
    response = client.responses.create(model=model, input=prompt)
    content = response.output_text.strip()
    if not content:
        raise RuntimeError("API 返回了空内容,请检查模型权限、额度和请求日志。")
    return content + "\n"

parse_args() 里增加模型参数:

Python 示例 · 只读
parser.add_argument(
    "--model",
    default=os.getenv("OPENAI_MODEL", "gpt-5.6-luna"),
)

main() 生成内容的部分改成:

Python 示例 · 只读
if args.mode == "openai":
    content = generate_with_openai(build_prompt(profile), args.model)
else:
    content = render_template_resume(profile)

运行前,把 resume_input.json 换成不含真实身份信息的测试数据:

python app.py --mode openai --output resume_ai.md

模型名称、账户可用范围和价格会变化;如果默认模型不可用,请从官方模型目录选择你账户可用的文本模型,并通过 OPENAI_MODEL--model 显式指定。当前 SDK 的安装、环境变量和 Responses API 结构以 OpenAI API 快速入门 为准,模型选择以 OpenAI 模型目录 为准。

4. API 结果必须人工验收

逐项问自己:

  • 每条经历都来自输入吗?
  • 模型有没有补出不存在的年份、指标或公司?
  • 模型有没有把“正在学习”写成“熟练掌握”?
  • 标题和 Markdown 结构是否完整?
  • 失败时,离线模式是否仍然可用?

Prompt 中写“不要虚构”不等于模型绝不会虚构。API 输出不能直接当作求职事实。

测试、Debug 与 Final Review

1. 建立最小自动测试

tests/test_app.py 中写:

Python 示例 · 只读
import json
import tempfile
import unittest
from pathlib import Path

from app import build_prompt, load_profile, render_template_resume


class ResumeGeneratorTests(unittest.TestCase):
    def setUp(self) -> None:
        self.profile = {
            "name": "小林",
            "target_role": "数据分析实习生",
            "education": "某大学 信息管理专业",
            "skills": ["Python", "SQL"],
            "projects": [{"name": "订单分析", "description": "清洗脱敏订单数据"}],
            "experience": ["使用 Python 整理课程数据"],
        }

    def test_template_contains_supplied_facts(self) -> None:
        output = render_template_resume(self.profile)
        for fact in ("小林", "数据分析实习生", "订单分析", "Python"):
            self.assertIn(fact, output)

    def test_prompt_forbids_fabrication(self) -> None:
        prompt = build_prompt(self.profile)
        self.assertIn("不要虚构", prompt)

    def test_missing_field_fails_early(self) -> None:
        invalid = dict(self.profile)
        invalid.pop("target_role")
        with tempfile.TemporaryDirectory() as temp_dir:
            path = Path(temp_dir) / "invalid.json"
            path.write_text(json.dumps(invalid, ensure_ascii=False), encoding="utf-8")
            with self.assertRaisesRegex(ValueError, "target_role"):
                load_profile(path)


if __name__ == "__main__":
    unittest.main()

运行:

python -m unittest discover -s tests -v

2. 做一次真实 Debug 闭环

不要只看全部通过。主动把 resume_input.jsonskills 改成字符串:

"skills": "Python, SQL"

按以下格式记录:

复现命令:python app.py --mode template
实际结果:字段 skills 应为 list
预期结果:skills 必须是 JSON 数组
根因:输入类型违反合同
最小修复:把 skills 恢复为字符串数组
回归证据:全部测试通过,两种离线模式再次成功

调试的目标不是“让红字消失”,而是保留一条别人能复现、能理解、能回归的证据链。

3. Final Review

在交付前检查最终改动:

  • 需求外功能是否被移除;
  • 是否还有硬编码路径或个人信息;
  • 所有输出是否使用 UTF-8;
  • API Key 是否只来自环境变量;
  • 自动测试和两条真实命令是否都运行过;
  • API 若没有真实调用,是否明确写为“未验证”;
  • README 是否让一个新用户能从零复现。

项目打包和分享

1. 写清 README

README 至少包含:项目目标、环境版本、文件结构、安装步骤、三种模式、测试命令、输入字段、隐私边界和已知限制。

推荐公开结构:

vibe-resume-generator/
├── app.py
├── resume_input.example.json
├── tests/
│   └── test_app.py
├── requirements.txt
├── README.md
├── WORKLOG.md
└── ACCEPTANCE.md

把脱敏示例重命名为 resume_input.example.json。真实简历输入、生成结果和 Key 不进入公开包。

2. 生成依赖说明

离线模式只使用 Python 标准库。若你完成了 API 加餐,在 requirements.txt 中写:

openai

同时在 README 说明依赖和模型可用性会变化,安装时应核对官方文档。不要把整个虚拟环境打进压缩包。

3. 在干净副本中复跑

分享前新建一个临时目录,只复制公开结构中的文件,然后重新运行:

python app.py --input resume_input.example.json --mode template
python app.py --input resume_input.example.json --mode prompt
python -m unittest discover -s tests -v

如果干净副本不能运行,项目还不能交付。

4. 压缩或上传 GitHub

macOS 或 Linux:

zip -r vibe-resume-generator.zip vibe-resume-generator \
  -x "*/.venv/*" "*/__pycache__/*" "*/.env" "*/resume*.md"

Windows PowerShell:先确认目录中没有 .venv.env 和真实输入,再运行:

Compress-Archive -Path vibe-resume-generator\* -DestinationPath vibe-resume-generator.zip

上传 GitHub 前再执行一次:

git status --short

逐个检查准备提交的文件。不要用一句“应该没有”代替检查。

实践项目: 个人简历自动生成器

现在关掉当前 AI 对话,开一个新对话,从空白上下文完成下面的挑战。

必做任务

  1. 用自己的脱敏信息重写输入 JSON;
  2. 独立说明 Goal、Context、Constraints 和 Acceptance;
  3. 让 AI 先只读探索并提交不超过 3 步的计划;
  4. 分两轮完成“读取与校验”“模板与保存”;
  5. 复现一次真实失败,完成最小修复和完整回归;
  6. 生成 resume.mdresume_prompt.txt
  7. 完成 README、测试记录和干净项目包;
  8. 可选:使用脱敏输入运行一次 API 模式并人工核对。

独立迁移挑战

不提供现成 Prompt。请在新对话中把 links 字段加入输入合同,让简历增加“作品链接”章节。你必须先写出新的验收标准,再允许 AI 修改代码。

迁移完成的最低证据:

  • 没有 links 时给出清楚错误,或明确把它设计为可选字段;
  • 有 1–3 个链接时能稳定输出;
  • 测试同时覆盖空列表和正常列表;
  • README 已同步更新;
  • 你能解释为什么选择必填或可选。

提交清单

  • app.py
  • resume_input.example.json
  • tests/test_app.py
  • README.md
  • WORKLOG.md
  • ACCEPTANCE.md
  • 测试通过的终端记录
  • 离线生成的脱敏结果
  • 一次失败复现与回归记录
  • 独立迁移挑战说明

当前限制与完成标准

这节课的必做路径只生成 Markdown,不处理 PDF 样式、ATS 评分、岗位投递或录取预测。API 输出也不保证比离线模板更好。

David Studio 当前已验证课程配套 Python 项目的离线生成、Prompt 导出、输入错误和无 Key 安全失败;没有在本次发布中使用真实 Key 进行联网 API 冒烟测试。学习者需要根据自己的账户、模型权限、额度和所在地区自行验证 API 模式。

真正的完成标准是:另一个人拿到你的干净项目包,可以按 README 在新环境中复现;你也能说明每一个范围取舍、失败和验证证据。