第 1 步:创建项目目录

这个创建环境和系统有关,我是Windows、安装了conda、vscode环境,下面的命令根据环境修改

# 创建项目目录
mkdir -p llm-week01
cd llm-week01
# 创建子目录
mkdir -p prompts
mkdir -p outputs
# 欢迎使用Markdown编辑器

第 2 步:注册## 标题并获取 API Key(

推荐:DeepSeek(国内友好、中文好、注册送 10 元)

  1. 打开 https://platform.deepseek.com/
  2. 手机号注册(10 秒)
  3. 登录后,左边菜单点 API keys → 创建 API key
  4. 给 key 起个名字(比如 week01-learning)

第 3 步:安装依赖

python --version

Insert image description here

我的系统里安装了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

Insert image description here

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()

Insert image description here

第 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 写一个快速排序函数。

请一步步思考:

  1. 先写出快速排序的算法步骤(用自然语言)
  2. 把每一步翻译成代码
  3. 把它们组合成完整函数
  4. 给一个测试用例验证"“”
    ● 激活词:“请一步步思考”、“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 = “”"按可能性从高到低排查:

  1. num_workers=0(最常见)→ 数据加载卡在主进程。验证:nvidia-smi 看 GPU 利用率是否周期性掉到 0。改 num_workers=4, pin_memory=True
  2. 单张图解码开销大 → 用 time.perf_counter() 单独计时 dataset[0],超过 10ms 就考虑预解码成 .npy 或用 DALI。
  3. 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 步:手动评分

  1. 先评前 5 条「锚定」——看完这 5 条,你心里就有 1-5 分的标准了
  2. 一次评完所有 A 策略、再评所有 B,再评 C——避免「对比效应」(评完 A 的 5 分后评 B 的 4 分会觉得 B 差,但其实差不多)
  3. 打分快,不纠结——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 = ?

更多推荐