大模型实战week01
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
-
Day 1 · 环境 + 第一次自动化脚本
第 1 步:创建项目目录
这个创建环境和系统有关,我是Windows、安装了conda、vscode环境,下面的命令根据环境修改
# 创建项目目录
mkdir -p llm-week01
cd llm-week01
# 创建子目录
mkdir -p prompts
mkdir -p outputs
# 欢迎使用Markdown编辑器
第 2 步:注册## 标题并获取 API Key(
推荐:DeepSeek(国内友好、中文好、注册送 10 元)
- 打开 https://platform.deepseek.com/
- 手机号注册(10 秒)
- 登录后,左边菜单点 API keys → 创建 API key
- 给 key 起个名字(比如 week01-learning)
第 3 步:安装依赖
python --version

我的系统里安装了conda,我用conda安装虚拟环境
# 1. Create environment, specifying Python 3.12.7 to match the current version
conda create -n llm-week01 python=3.12.7 -y
# 2. Activate the environment (run directly in Windows PowerShell)
conda activate llm-week01

pip install openai python-dotenv
第 4 步:配置环境变量
cd llm-week01
# Replace sk-xxx with your actual key from the DeepSeek console
echo "DEEPSEEK_API_KEY=sk-your-actual-key" > .env
# Verify the file was created successfully immediately
Get-ChildItem -Force .env
# You should see something like: -rw-r--r-- ... .env
Get-Content .env
# You should see: DEEPSEEK_API_KEY=sk-xxx...
This list tells us which files in the project must never be shared, uploaded to cloud storage, or sent to anyone.
New-Item .exclude-list.txt
下面是我的输出
(llm-week01) PS D:\my_project\python\LargeModelPracticalCombat> New-Item .exclude-list.txt
>>
Directory: D:\my_project\python\LargeModelPracticalCombat
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2026/8/7 17:22 0 .exclude-list.txt
(llm-week01) PS D:\my_project\python\LargeModelPracticalCombat>
编辑exclude-list.txt:
touch .exclude-list.txt
第 5 步:写第一个能跑通的脚本
# chat.py
# Day 1 · 第一次让 LLM 帮你润色项目文档
import os
from openai import OpenAI
from dotenv import load_dotenv
# 加载 .env 中的环境变量
load_dotenv()
# 创建客户端
# DeepSeek 兼容 OpenAI SDK,只需要换 base_url 和 model
client = OpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
)
def ask(question: str, temperature: float = 0.5) -> dict:
"""调用 LLM,返回完整响应对象"""
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": question}],
temperature=temperature,
)
return resp
def main():
# 一段粗糙的 README 草稿(你日常会写的真实场景)
rough_readme = """这是我的项目。功能是处理 VisDrone 数据集。跑 baseline。
之前用 YOLOv8,现在要试试 YOLOv9。代码有点乱。GPU 是一张 A100。
队友不知道从哪开始。需要装个环境。需要装 torch。需要 pip install。
跑起来大概要 1 小时。"""
prompt = f"""你是一个科研项目文档润色助手。任务是把下面这段粗糙的项目说明润色成清晰的 README 草稿。
要求:
- 用 Markdown 格式
- 包含"项目目标 / 技术栈 / 硬件需求 / 快速开始 / 当前进度"5 个段落
- 保留所有具体信息(模型名、数据集名、硬件等)
- 不要编造代码示例
原始草稿:
{rough_readme}
请润色输出:"""
print(f"\n输入的粗糙草稿:\n{rough_readme}\n")
print("=" * 60)
resp = ask(prompt)
# 提取答案
answer = resp.choices[0].message.content
print(f"\n润色后的 README 草稿:\n{answer}\n")
# 打印用量信息
print("-" * 60)
print(f"输入 tokens: {resp.usage.prompt_tokens}")
print(f"输出 tokens: {resp.usage.completion_tokens}")
print(f"总计 tokens: {resp.usage.total_tokens}")
print(f"模型: {resp.model}")
print(f"finish_reason: {resp.choices[0].finish_reason}")
if __name__ == "__main__":
main()

第 6 步:理解响应结构
(llm-week01) PS D:\my_project\python\LargeModelPracticalCombat> python
Python 3.12.7 | packaged by Anaconda, Inc. | (main, Oct 4 2024, 13:17:27) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from chat import ask
>>> resp = ask("你好")
>>> type(resp)
<class 'openai.types.chat.chat_completion.ChatCompletion'>
>>> resp.choices
[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='你好!很高兴见到你,有什么我可以帮忙的吗?', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None))]
>>> resp.choices[0]
Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='你好!很高兴见到你,有什么我可以帮忙的吗?', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None))
>>> resp.choices[0].message
ChatCompletionMessage(content='你好!很高兴见到你,有什么我可以帮忙的吗?', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None)
>>> resp.choices[0].message.role
'assistant'
>>> resp.choices[0].message.content
'你好!很高兴见到你,有什么我可以帮忙的吗?'
>>> resp.usage
CompletionUsage(completion_tokens=11, prompt_tokens=5, total_tokens=16, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetails(audio_tokens=None, cache_write_tokens=None, cached_tokens=0), prompt_cache_hit_tokens=0, prompt_cache_miss_tokens=5)
>>> resp.usage.total_tokens
16
>>> resp.model
'deepseek-v4-flash'
>>>
第 7 步:尝试 5 个不同类型的工程任务
修改 chat.py 的 prompt 部分,每次跑一种类型,至少跑 5 个不同类型的 prompt:
任务类型 你日常会做的例子
1 文档润色 “把这段粗糙的 README 改成清晰版”
2 代码生成 “用 Python 写一个快速排序”
3 代码解释 “解释这段代码 [一段复杂代码]”
4 Bug 分析 “我训练 loss 不下降,可能原因?”
5 英语翻译 “把这段中文邮件翻译成英文给国外导师”
第 8 步:理解 token 计费
import tiktoken
from datetime import datetime
def count_tokens(text: str, model: str = "gpt-3.5-turbo") -> int:
enc = tiktoken.encoding_for_model(model)
return len(enc.encode(text))
# 测试
text = "用一段话解释 Transformer 的 self-attention"
print(f"{count_tokens(text)} tokens")
print(datetime.now())
# 约 15-20 tokens

Day 2 · system prompt + 多轮对话
第 1 步:理解三种 role
from chat import client # 复用 Day 1 的 client
from datetime import datetime
# --- role 1: system ---
# 设定助手的"人格 / 行为规则 / 输出格式"
# 这条消息不会显示给用户,但它在每轮对话里都"在场"
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "你是写诗助手。所有回答都用一首 4 行短诗,主题贴合问题。"},
{"role": "user", "content": "Python 里 list 和 tuple 有什么区别?"},
],
)
print("=== system=诗人 ===")
print(resp.choices[0].message.content)
print(f"时间: {datetime.now()}")
第 2 步:理解多轮对话的本质
最关键的认知:
LLM 没有记忆。每次请求都要把完整的对话历史塞进 messages。
这听起来很蠢,但这就是现在所有 LLM 应用(包括 ChatGPT 网页版)的工作方式。
# 想象这是 3 轮对话
messages = [
{"role": "system", "content": "你是代码 debugger。"},
{"role": "user", "content": "我这段代码 loss 不下降:[代码]"},
{"role": "assistant", "content": "看起来是 LR 太高了。试着降到 0.001。"},
{"role": "user", "content": "我试了 0.001 还是不下降呢?"}, # 第二轮提问
# 注意:必须包含第一轮的所有消息
]
第 3 步:写多轮对话脚本
# research_chat.py
# Day 2 · 多轮 prompt 调优工作流
import os
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",
)
# ↓↓↓ 改成你工程自动化场景的 system prompt ↓↓↓
SYSTEM_PROMPT = """你是 prompt 调优助手,帮我迭代 prompt 让 LLM 输出更符合预期。
工作流程:
- 用户给你初始 prompt + 测试样例
- 你运行这个 prompt(你自己推理)
- 如果输出不满足,告诉我问题在哪、改哪一句
- 给改后的版本,附 1-2 句话解释
回答风格:
- 简洁,不超过 200 字
- 中文回答
- 直接说"改成 X",不啰嗦"""
def main():
# 初始化 messages
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
print("=" * 60)
print("Prompt 调优助手已启动(输入 'q' 或 '退出' 结束)")
print("=" * 60)
print(f"\n示例对话:粘一段 prompt + 3 个测试用例,助手会分析 + 改写\n")
round_num = 0
while True:
try:
user_input = input("你: ").strip()
except (EOFError, KeyboardInterrupt):
print("\n[程序退出]")
break
if user_input.lower() in ("q", "quit", "exit", "退出"):
print(f"\n[本次对话共 {round_num} 轮]")
break
if not user_input:
continue
messages.append({"role": "user", "content": user_input})
round_num += 1
try:
resp = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
temperature=0.5,
)
except Exception as e:
print(f"\n[错误] {e}")
messages.pop()
continue
answer = resp.choices[0].message.content
print(f"\n助手: {answer}\n")
messages.append({"role": "assistant", "content": answer})
# 调试用
print(f" [debug] messages 长度: {len(messages)}")
if __name__ == "__main__":
main()

第 4 步:5 轮对话实验.
你: 我想让 LLM 自动给 Python 项目写 commit message。我的 prompt 是:"给这个 diff 写 commit message: [diff 内容]。要求简洁,不超过 50 字。"。但输出经常太长,写成 [feat] add new feature 也不够规范。
助手: 你的 prompt 问题在 [具体问题分析]...
你: 那我应该怎么改?
助手: 改成 "..."
你: 改完后再给几个测试用例。
助手: 好的,3 个测试用例:[具体例子]
你: 我用 git log 看历史 commit,发现实际风格是 [某种习惯]。需要再调整吗?
助手: 是的,你的 prompt 里应该加一句 [建议]
你: 还有别的常见错误吗?
助手: 还有 [其他问题]
第 5 步:验证"如果忘了 append 会怎样
cp Multi-turnDialogueDcript.py Multi-turnDialogueDcriptbroken.py
编辑 Multi-turnDialogueDcriptbroken,注释掉 这一行:
# messages.append({"role": "assistant", "content": answer}) # ← 注释掉
跑 python Multi-turnDialogueDcriptbroken,问同样的问题:
你: 我想让 LLM 自动给 Python 项目写 commit message...
助手: 好的,commit message 一般包括 [通用回答]
你: 我的 prompt 是 X,输出太长。怎么改?
助手: 这个要看具体情况...(此时还能看到第 1 轮)
你: 还有别的常见错误吗?
助手: [现在问的是"代码相关"问题,但助手已经忘了前面在聊 commit message 了]

第 6 步:打磨你的工程自动化 system prompt
回到 Multi-turnDialogueDcript.py,把 SYSTEM_PROMPT 改得更符合你真实的工程自动化需求。模板:
SYSTEM_PROMPT = """你是我的自动化工程助手,专攻 [你的工程场景,比如:Python 工具开发 / Git 工作流优化 / 论文 LaTeX 排版]。
我的背景:
- [比如:研二学生 / 博一]
- [比如:日常写 Python 工具、做实验、写文档]
- [比如:常用 PyCharm / VSCode / Vim]
回答风格:
- 简洁准确,每条回答不超过 200 字
- 给代码示例时使用 Markdown 代码块
- 解释"为什么这样写",不只是给代码
- 不确定时直接说"我不确定",不要硬编
输出格式(如果适用):
- 比较/列表类问题用 Markdown 列表
- 步骤类问题用编号列表
- 总结类问题用 1-2 段文字 + 关键 bullet"""
-
Day 3 · 参数 + Prompt 三板斧
第 1 步:玩 temperature
temperature 是什么:
● 控制模型输出的随机性
● 范围 0-2(OpenAI)/ 0-1.5(DeepSeek)
● 低温度(0-0.3) → 稳定、可复现、几乎同样的问题每次都差不多
● 高温度(0.7-1.2) → 随机、有创造性、同一问题每次都不一样
# param_temperature.py
# Day 3 · 玩 temperature 参数
import os
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",
)
# 选一个"工程自动化"场景的开放性问题——有随机性才能看出参数效果
# ↓↓↓ 换成你日常让 LLM 做的真问题 ↓↓↓
QUESTION = "给我的 Python 项目写一个 README。要求:1) 项目目标 2) 安装步骤 3) 用法示例 4) 贡献指南"
SYSTEM = "你是资深 Python 工程师,写文档简洁专业。"
TEMPERATURES = [0.0, 0.3, 0.7, 1.2]
def ask(question: str, temperature: float) -> str:
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": question},
],
temperature=temperature,
)
return resp.choices[0].message.content
def main():
for temp in TEMPERATURES:
print(f"\n{'=' * 60}")
print(f"### temperature = {temp}")
print(f"{'=' * 60}\n")
# 每个温度跑 2 次,看是不是真的不同
for run in range(1, 3):
print(f"--- 第 {run} 次 ---")
print(ask(QUESTION, temp))
print()
if __name__ == "__main__":
main()

第 2 步:玩 top_p
top_p 是什么:
● 核采样阈值(Nucleus Sampling)
● 范围 0-1
● 模型在生成每个字时,先按概率从高到低排序,累加概率到 top_p 截止
● top_p=0.1 → 只考虑概率最高的极少数词 → 非常确定
● top_p=0.9 → 考虑大部分合理词 → 平衡(默认)
● top_p=1.0 → 考虑所有词 → 最随机
创建 param_top_p.py
# param_top_p.py
# Day 3 · 玩 top_p 参数
import os
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",
)
# 用同样的开放性问题,但固定 temperature=0.7
# (用真实问题,不要留占位符——否则 LLM 会原样回显)
QUESTION = "写一个 Python 函数,实现 [1, 2, [3, 4, [5, 6]]] 这种嵌套列表的展平"
SYSTEM = "你是资深 Python 工程师,写代码简洁有注释。"
TOP_PS = [0.5, 0.9, 1.0]
TEMP = 0.7
def ask(question: str, top_p: float) -> str:
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": question},
],
temperature=TEMP,
top_p=top_p,
)
return resp.choices[0].message.content
def main():
for top_p in TOP_PS:
print(f"\n{'=' * 60}")
print(f"### temperature = {TEMP}, top_p = {top_p}")
print(f"{'=' * 60}\n")
print(ask(QUESTION, top_p))
if __name__ == "__main__":
main()
第 3 步:理解 Prompt 三板斧
为什么需要 Prompt 工程:同一道题,写法不同输出天差地别。下面三种套路覆盖 90% 场景。
板斧 1:直接提问(Zero-shot)
prompt = “用 Python 写一个快速排序函数”
● 最简单,但最不可控
● 适合:闲聊、简单问答、初稿
板斧 2:思维链(Chain-of-Thought, CoT)
prompt = “”"用 Python 写一个快速排序函数。
请一步步思考:
- 先写出快速排序的算法步骤(用自然语言)
- 把每一步翻译成代码
- 把它们组合成完整函数
- 给一个测试用例验证"“”
● 激活词:“请一步步思考”、“Let’s think step by step”、“请分步骤考虑”
● 适合:debug、代码审查、复杂分析
● 原理:让模型"先想后答",避免跳步错误
板斧 3:Few-shot(In-Context Learning)
prompt = “”"你是写严谨 Python 代码的工程师,输出必须带类型注解和 docstring。
示例 1:用 Python 写一个二分搜索函数。
from typing import List
def binary_search(arr: List[int], target: int) -> int:
"""
在有序数组中查找 target 的索引。
Args:
arr: 有序整数数组
target: 要查找的目标值
Returns:
target 的索引;如果不存在返回 -1
"""
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
“”"
● 先给 1-5 个"输入 → 输出"示例,模型会模仿格式和风格
● 适合:输出格式固定、批处理任务、风格统一
● 原理:示例即"上下文学习",模型不需要重新训练就能学会新模式
第 4 步:用真实工程任务做 Prompt 对比
# research_compare.py
# Day 4 · 你的第一个自动化 Prompt 实验
import os
import csv
import sys
import time
from typing import Dict, List, Optional, Tuple
from datetime import datetime
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
API_KEY=os.getenv("DEEPSEEK_API_KEY"),
if not API_KEY:
raise SystemExit(
"未找到 DEEPSEEK_API_KEY。\n"
"请在项目根目录建 .env,写入:DEEPSEEK_API_KEY=sk-xxxxxxxx"
)
client = OpenAI(api_key=API_KEY, base_url="https://api.deepseek.com")
MODEL = "deepseek-chat"
TEMPERATURE = 0.5
SLEEP_BETWEEN_CALLS = 0.5 # 秒,避免打太快触发限流
MAX_RETRY = 2 # 单次调用失败后的重试次数
# 先小规模试跑:命令行传数字即可,例如 python research_compare.py 2
# 不传参数则跑全部 10 题。
LIMIT: Optional[int] = int(sys.argv[1]) if len(sys.argv) > 1 else None
# ==================== 你只需要改这一块 ====================
RESEARCH_DOMAIN = "Python 工具开发"
QUESTIONS: List[Dict[str, str]] = [
# --- 代码生成 ---
{"id": "Q1", "type": "代码生成", "question": "写一个检测重复文件的小工具"},
{"id": "Q2", "type": "代码生成", "question": "写一个命令行参数解析器(用 argparse)"},
# --- Bug 分析 ---
{"id": "Q3", "type": "Bug 分析", "question": "我的训练 loss 卡在 2.5 不下降,可能原因?"},
{"id": "Q4", "type": "Bug 分析", "question": "我的显存 OOM,怎么办?"},
# --- README 生成 ---
{"id": "Q5", "type": "README 生成",
"question": "给一个 YOLOv8 检测项目写 README,要求包括项目目标 / 安装 / 快速开始 / 训练 / 评估"},
{"id": "Q6", "type": "README 生成",
"question": "把这段代码改写成 API 文档:def add(a,b): return a+b"},
# --- Git commit ---
{"id": "Q7", "type": "Git commit",
"question": "给这个 diff 写 commit message:+def hello(): print('hi')"},
{"id": "Q8", "type": "Git commit",
"question": "把这条 commit 改写成 PR 描述:fix: handle empty input"},
# --- 数据总结 ---
{"id": "Q9", "type": "数据总结",
"question": "总结这段训练日志的关键指标:loss: 2.5->0.87, mAP: 0.05->0.421, time: 41min"},
{"id": "Q10", "type": "数据总结",
"question": "把这段代码总结成 1 句话功能描述:def send_email(to,subj,body): ..."},
]
SYSTEM_TEMPLATE = f"""你是 {RESEARCH_DOMAIN} 方向的资深工程师。
回答要求:
- 简洁准确,每条回答不超过 200 字
- 代码示例用 Markdown 代码块,必须带类型注解
- 引用论文/工具时给出 作者(年份)/官方文档 格式
- 不确定时直接说"不确定"
- 默认中文,专业术语保留英文"""
# ---------- Few-shot 示例库 ----------
# ⚠️ 铁律:下面这些字符串里含花括号(代码),
# 绝不能加 f 前缀,也绝不能对它们调 .format()。
# 需要插值的部分一律用字符串拼接(见 build_prompts)。
#
# ⚠️ 另一条铁律:示例问题不能和 QUESTIONS 里的题目重复,
# 否则策略 C 就是「先给答案再问同一题」,比较结果失真。
_EX_CODE_Q = "写一个批量给文件名加日期前缀的小工具"
_EX_CODE_A = '''```python
from datetime import datetime
from pathlib import Path
from typing import List
def add_date_prefix(dir_path: str, pattern: str = "*") -> List[str]:
"""给目录下匹配的文件加上 YYYYMMDD_ 前缀。
Args:
dir_path: 目标目录。
pattern: glob 模式,默认匹配全部文件。
Returns:
重命名后的新路径列表;无文件被改则为空列表。
"""
stamp = datetime.now().strftime("%Y%m%d")
renamed: List[str] = []
for p in Path(dir_path).glob(pattern):
if p.is_file() and not p.name.startswith(stamp):
new_p = p.with_name(stamp + "_" + p.name)
p.rename(new_p)
renamed.append(str(new_p))
return renamed
要点:pathlib 遍历、类型注解完整、docstring 写清返回值语义、幂等(已加前缀的跳过)。‘’’
_EX_BUG_Q = “我的 DataLoader 迭代很慢,可能原因?”
_EX_BUG_A = “”"按可能性从高到低排查:
- num_workers=0(最常见)→ 数据加载卡在主进程。验证:
nvidia-smi看 GPU 利用率是否周期性掉到 0。改num_workers=4, pin_memory=True。 - 单张图解码开销大 → 用
time.perf_counter()单独计时dataset[0],超过 10ms 就考虑预解码成.npy或用 DALI。 - transform 里有 CPU 重操作(如大尺寸 resize 前置)→ 调整顺序,先 crop 再 resize。
先做第 1 条,多数情况到这里就解决了。“”"
_EX_README_Q = “给一个命令行截图工具写 README 的「快速开始」一节”
_EX_README_A = “”"## 快速开始
pip install shotkit
shotkit capture --output ./shots --delay 3
| 参数 | 说明 | 默认值 |
|---|---|---|
--output |
截图保存目录 | ./shots |
--delay |
延迟秒数 | 0 |
产物为 shots/YYYYMMDD_HHMMSS.png。若提示权限错误,macOS 需在
「系统设置 → 隐私与安全性 → 屏幕录制」中授权终端。“”"
_EX_COMMIT_Q = “给这个 diff 写 commit message:- timeout=5 → + timeout=30”
_EX_COMMIT_A = “”"```
fix(http): raise default request timeout from 5s to 30s
Slow upstream responses were being cut off, surfacing as
spurious ConnectionError in production logs.
遵循 Conventional Commits:`type(scope): 祈使句摘要`(≤50 字符),空行后正文说明**为什么**改,而不是重复改了什么。"""
_EX_SUMMARY_Q = "总结这段日志:epoch 12/50, train_acc: 0.61->0.88, val_acc: 0.59->0.63"
_EX_SUMMARY_A = """**关键指标**:train_acc 0.61→0.88(+0.27),val_acc 0.59→0.63(+0.04),进度 12/50 epoch。
**判读**:训练集涨幅是验证集的 6.75 倍,train-val gap 已达 0.25 —— **典型过拟合信号**,且才跑到 24% 进度。
**建议**:加 early stopping 监控 val_acc,或补 augmentation / weight decay。继续训到 50 epoch 大概率只会拉大 gap。"""
FEWSHOT_BY_TYPE: Dict[str, Tuple[str, str]] = {
"代码生成": (_EX_CODE_Q, _EX_CODE_A),
"Bug 分析": (_EX_BUG_Q, _EX_BUG_A),
"README 生成": (_EX_README_Q, _EX_README_A),
"Git commit": (_EX_COMMIT_Q, _EX_COMMIT_A),
"数据总结": (_EX_SUMMARY_Q, _EX_SUMMARY_A),
}
DEFAULT_FEWSHOT: Tuple[str, str] = (_EX_CODE_Q, _EX_CODE_A)
# ==================== 下面不用动 ====================
def build_prompts(question: str, qtype: str) -> Dict[str, str]:
"""为单个问题构建三种 Prompt 策略。
Args:
question: 问题正文。
qtype: 问题类型,用于挑选同类型的 few-shot 示例。
Returns:
策略名 -> prompt 文本。
"""
ex_q, ex_a = FEWSHOT_BY_TYPE.get(qtype, DEFAULT_FEWSHOT)
# 关键:ex_a 里含花括号,全程只做拼接,不经 f-string / .format
prompt_c = (
f"你是 {RESEARCH_DOMAIN} 方向的资深工程师,回答结构化、可执行。\n\n"
f"示例问题:{ex_q}\n"
"示例答案:\n"
+ ex_a
+ f"\n\n现在请用同样的风格回答:\n{question}"
)
return {
"A_直接提问": question,
"B_思维链": question + "\n\n请一步步分析,先给思路,再给完整代码。",
"C_Few-shot": prompt_c,
}
def ask(prompt: str) -> Dict[str, object]:
"""调用一次模型,失败自动重试。
Returns:
dict:answer / prompt_tokens / completion_tokens / total_tokens /
elapsed / error(成功时为 "")
"""
last_err = ""
for attempt in range(MAX_RETRY + 1):
t0 = time.perf_counter()
try:
resp = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": SYSTEM_TEMPLATE},
{"role": "user", "content": prompt},
],
temperature=TEMPERATURE,
)
elapsed = time.perf_counter() - t0
usage = getattr(resp, "usage", None)
return {
"answer": resp.choices[0].message.content or "",
"prompt_tokens": getattr(usage, "prompt_tokens", None),
"completion_tokens": getattr(usage, "completion_tokens", None),
"total_tokens": getattr(usage, "total_tokens", None),
"elapsed": elapsed,
"error": "",
}
except Exception as exc:
last_err = f"{type(exc).__name__}: {exc}"
if attempt < MAX_RETRY:
wait = 2 ** attempt
print(f" [重试 {attempt + 1}/{MAX_RETRY}] {last_err}({wait}s 后重来)")
time.sleep(wait)
return {
"answer": "", "prompt_tokens": None, "completion_tokens": None,
"total_tokens": None, "elapsed": 0.0, "error": last_err,
}
FIELDNAMES = [
"question_id", "question_type", "question", "strategy",
"prompt_tokens", "completion_tokens", "total_tokens",
"elapsed_sec", "answer_chars", "answer_preview",
"quality_score_1to5", # 手动填 1-5
"notes", # 手动填
"error",
]
def main() -> None:
os.makedirs("outputs", exist_ok=True)
csv_path = "outputs/day04_results.csv"
md_path = "outputs/day04_answers.md"
questions = QUESTIONS[:LIMIT] if LIMIT else QUESTIONS
print(f"本次运行:{len(questions)} 题 × 3 策略 = {len(questions) * 3} 次调用")
if LIMIT:
print("(小规模试跑模式;去掉命令行参数即跑全部)")
rows: List[Dict[str, object]] = []
# utf-8-sig:Windows 上 Excel 直接双击打开中文不乱码
with open(csv_path, "w", encoding="utf-8-sig", newline="") as fcsv, \
open(md_path, "w", encoding="utf-8") as fmd:
writer = csv.DictWriter(fcsv, fieldnames=FIELDNAMES)
writer.writeheader()
fmd.write(f"# Day 4 · Prompt 策略对比全文记录\n\n"
f"- 模型:`{MODEL}` 温度:{TEMPERATURE}\n"
f"- 领域:{RESEARCH_DOMAIN}\n\n")
for q in questions:
print("\n" + "#" * 70)
print(f"### {q['id']} · {q['type']}")
print(f"### 问题: {q['question']}")
print("#" * 70)
fmd.write(f"\n---\n\n## {q['id']} · {q['type']}\n\n**问题**:{q['question']}\n")
for strategy_name, prompt in build_prompts(q["question"], q["type"]).items():
print(f"\n--- {strategy_name} ---")
r = ask(prompt)
answer = str(r["answer"])
if r["error"]:
print(f"[失败] {r['error']}")
else:
print(f"[prompt={r['prompt_tokens']} completion={r['completion_tokens']} "
f"total={r['total_tokens']} | {r['elapsed']:.1f}s]")
print(answer[:200] + ("..." if len(answer) > 200 else ""))
row = {
"question_id": q["id"],
"question_type": q["type"],
"question": q["question"],
"strategy": strategy_name,
"prompt_tokens": r["prompt_tokens"],
"completion_tokens": r["completion_tokens"],
"total_tokens": r["total_tokens"],
"elapsed_sec": round(float(r["elapsed"]), 2),
"answer_chars": len(answer),
"answer_preview": answer[:100].replace("\n", " "),
"quality_score_1to5": "",
"notes": "",
"error": r["error"],
}
rows.append(row)
# 逐条落盘:中途 Ctrl-C 或崩溃都不丢已完成的部分
writer.writerow(row)
fcsv.flush()
fmd.write(f"\n### {strategy_name}\n\n")
fmd.write(f"> tokens: {r['total_tokens']} 耗时: {r['elapsed']:.1f}s\n\n")
fmd.write((answer if answer else f"**调用失败**:{r['error']}") + "\n")
fmd.flush()
time.sleep(SLEEP_BETWEEN_CALLS)
# ---- 汇总 ----
print("\n" + "=" * 70)
print("### 按策略汇总(均值)")
print("=" * 70)
print(f"{'策略':<16}{'prompt':>9}{'completion':>12}{'total':>9}"
f"{'耗时(s)':>10}{'回答字数':>10}{'失败':>6}")
for name in ("A_直接提问", "B_思维链", "C_Few-shot"):
sub = [r for r in rows if r["strategy"] == name]
ok = [r for r in sub if not r["error"]]
if not ok:
print(f"{name:<16}{'-':>9}{'-':>12}{'-':>9}{'-':>10}{'-':>10}{len(sub):>6}")
continue
def avg(key: str) -> float:
vals = [float(r[key]) for r in ok if r[key] is not None]
return sum(vals) / len(vals) if vals else 0.0
print(f"{name:<16}{avg('prompt_tokens'):>9.0f}{avg('completion_tokens'):>12.0f}"
f"{avg('total_tokens'):>9.0f}{avg('elapsed_sec'):>10.1f}"
f"{avg('answer_chars'):>10.0f}{len(sub) - len(ok):>6}")
n_fail = sum(1 for r in rows if r["error"])
print("\n" + "=" * 70)
print(f"完成 {len(rows)} 条(失败 {n_fail} 条)")
print(f"CSV(打分用): {csv_path}")
print(f"MD (看全文): {md_path}")
print("=" * 70)
print("\n下一步:打开 CSV,逐行在 quality_score_1to5 填 1-5 分,再对比三种策略。")
print("注意 C 的 prompt_tokens 天然更高,比较时看 completion 质量 ÷ total_tokens 的性价比。")
print(datetime.now())
if __name__ == "__main__":
main()
第 3 步:跑实验
python research_compare.py
预期:脚本会跑 30 次 API 调用(10 任务 × 3 策略),每条都打印前 200 字。总耗时约 5-15 分钟(DeepSeek 大约 3-5s 一次)。
跑完会看到:




第 4 步:手动评分
- 先评前 5 条「锚定」——看完这 5 条,你心里就有 1-5 分的标准了
- 一次评完所有 A 策略、再评所有 B,再评 C——避免「对比效应」(评完 A 的 5 分后评 B 的 4 分会觉得 B 差,但其实差不多)
- 打分快,不纠结——30 条 30 分钟内评完,每条不超过 1 分钟
第 5 步:分析结果
在 outputs/day04_analysis.md 写一段分析:
# Day 4 · 实验分析
## 按策略汇总
| 策略 | 平均 tokens | 平均得分 | 适用类型 |
|---|---|---|---|
| A 直接 | | | |
| B CoT | | | |
| C Few-shot | | | |
## 按问题类型看最佳策略
| 类型 | 最佳策略 | 原因 |
|---|---|---|
| 代码生成 | | |
| Bug 分析 | | |
| README 生成 | | |
| Git commit | | |
| 数据总结 | | |
## 关键发现
- [例如:B 策略在所有类型上得分都比 A 高 1 分以上,CoT 是工程场景的万金油]
- [例如:C 策略在 Git commit / README 上最强,格式模仿精准]
- [例如:A 策略在简单问题上反而最省 tokens,可以省 30% 费用]
## 我的 Prompt 模板库(沉淀)
保存到 `prompts/research/`:
1. `cot_eng_universal.txt`(适合所有工程任务的万能 CoT)
2. `fewshot_code_with_types.txt`(代码生成专用,含类型注解规范)
3. `fewshot_git_commit.txt`(commit msg 专用)
4. `fewshot_readme.txt`(README 生成专用)
5. `direct_fact.txt`(简单事实问答,省 tokens)
[后面会陆续增加]
第 6 步:沉淀 Prompt 模板
mkdir -p prompts/research
模板示例 1:prompts/research/cot_eng_universal.txt
[问题]
请一步步分析:
1. 先给思路,再给代码
2. 代码必须带类型注解
3. 不确定直接说
模板示例 2:prompts/research/fewshot_code_with_types.txt
你是写 Python 项目的工程师。生成符合 Conventional Commits 规范的 commit message。
示例:diff = "+def login(user, pwd): ..." → commit = "feat(auth): add login function with basic validation"
示例:diff = "-old_func()\\n+new_func()" → commit = "refactor: replace old_func with new_func"
示例:diff = "+# TODO: implement cache" → commit = "docs: add cache TODO comment"
现在请回答:
diff = [你的 diff 内容]
commit = ?
更多推荐
所有评论(0)