三、《从零手撸 Agent》 · system prompt 与核心参数:调好你的旋钮

30 阅读6分钟

上一篇讲了 messages 的骨架。这一篇深入两个可调控的东西:system prompt 怎么写才有效,以及 API 的几个关键参数到底在控制什么。参数部分全部配实验,建议边跑边读。

一、system prompt:写给模型的” 岗位说明书”

1.1 一个对比例子

不用 system prompt:

messages = [{"role": "user", "content": "帮我看看这段代码有什么问题:x = 5 / 0"}]

回答可能很啰嗦:先讲什么是除法,再讲异常处理体系,最后才说 "你除以零了"

用 system prompt:

messages = [
    {"role": "system", "content": "你是一个代码审查员。只指出问题和修法,每条不超过两行,不讲背景知识。"},
    {"role": "user", "content": "帮我看看这段代码有什么问题:x = 5 / 0"},
]

回答:除以零错误。改为先判断除数是否为 0,或用 try/except 捕获 ZeroDivisionError。

同样的问题,输出天差地别。system prompt 决定模型” 用什么身份、什么方式” 回答。

1.2 一个实用的 system prompt 模板

有效的 system prompt 通常包含这四块(按需取舍):

SYSTEM_PROMPT = """
# 角色
你是一个Python代码审查助手。

# 行为规则
1. 只指出问题,不重写整个文件
2. 每个问题格式:[严重程度] 位置 - 问题 - 建议修法
3. 不确定的地方明确说"不确定",禁止猜测

# 输出格式
用Markdown列表输出,最多5条,按严重程度排序。

# 边界
如果代码与Python无关,回复"仅支持Python代码"。
"""

四块分别是:角色(你是谁)→ 规则(怎么做)→ 格式(输出长什么样)→ 边界(什么时候拒绝)。

1.3 写 system prompt 的三个新手误区

  • 误区一:堆形容词。“你是一个非常非常专业的专家”—— 没用。模型不因为夸它而变强。要写可执行的规则,不写情绪。
  • 误区二:规则互相矛盾。“回答要详尽” + “不超过 50 字”—— 模型只能随机服从一个。写完通读一遍,删掉冲突项。
  • 误区三:用否定句写期望行为。“不要啰嗦” 不如” 每条不超过两行”。模型对正向指令的服从度更高;否定句只在划红线时用(“禁止编造 API 名称”)。

二、参数详解:每个都做实验

2.1 temperature:随机性旋钮

原理一句话:模型每生成一个 token,其实是对所有候选词算出一个概率分布;temperature 决定从这个分布里抽样时的” 冒险程度”。

  • 0:永远选概率最高的词 → 同样输入几乎得到同样输出(确定性)
  • 越高(如 1.5):低概率词也有机会被选中 → 输出多变、有创意、也更不稳定

实验代码(同一个刁钻问题,不同 temperature 各跑 3 次):

def ask(temp: float) -> str:
    resp = client.chat.completions.create(
        model="deepseek-chat",
        temperature=temp,
        messages=[{"role": "user", "content": "用一个比喻解释什么是递归,一个词的比喻也算"}],
    )
    return resp.choices[0].message.content[:30]

for t in (0, 0.7, 1.5):
    print(f"--- temperature={t} ---")
    for _ in range(3):
        print(" ", ask(t))

你会观察到:t=0 时三次输出几乎相同;t=1.5 时三次各不相同,而且偶尔出现奇怪的比喻。

怎么选:

场景推荐原因
代码生成、数据抽取、分类0 ~ 0.3要稳定可复现
日常对话、总结改写0.5 ~ 0.8平衡
创意写作、起名字0.9 ~ 1.2要发散

本系列属于 Agent 开发,几乎全程 0~0.3—— 你要的是一个可靠的执行者,不是艺术家。

2.2 max_tokens:输出长度上限

resp = client.chat.completions.create(
    model="deepseek-chat",
    messages=[...],
    max_tokens=100,   # 回答最多100个token,超过就硬截断
)

两个用途:

  1. 省钱护栏:防止模型失控输出长文
  2. 强制造短回答:配合” 用一句话回答” 的提示效果更好

注意一个坑:如果输出被截断,resp.choices [0].finish_reason 会是 "length" 而不是 "stop"。调试时发现回答莫名断在半句话,先检查这个字段:

print(resp.choices[0].finish_reason)  # "stop"=正常结束  "length"=被截断

2.3 model:选哪个模型

同一家通常有多个模型档位(如 deepseek-chat /deepseek-reasoner),规律:

  • 普通对话模型:快、便宜,适合 Agent 里的常规节点
  • 推理模型(reasoner 类):贵、慢,但复杂问题正确率高,第 09 篇详解

工程经验:不要全程用最贵的。路由 / 格式化 / 简单判断用便宜模型,关键决策才上推理模型 —— 这是成本工程的基本功。

2.4 stream:流式开关

stream=True # 逐 token 返回(第 04 篇整篇讲它) 这里先记住参数名即可。

三、把参数和 system prompt 组合成一个可复用的函数

从这篇开始,我们会反复用到一个封装好的 chat () 函数,建议存成 llm[_utils.py](_utils.py),后续文章直接 import:

# llm_utils.py
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OPENAI_API_KEY"],
    base_url="https://api.deepseek.com",
)

def chat(
    messages: list[dict],
    model: str = "deepseek-chat",
    temperature: float = 0.3,
    max_tokens: int = 1024,
) -> str:
    """统一封装的LLM调用。Agent开发默认低temperature。"""
    resp = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=temperature,
        max_tokens=max_tokens,
    )
    if resp.choices[0].finish_reason == "length":
        print("[警告] 输出被max_tokens截断")
    return resp.choices[0].message.content

# 用法示例:
if __name__ == "__main__":
    print(chat([
        {"role": "system", "content": "你是简洁的编程助手。"},
        {"role": "user", "content": "一句话解释什么是异步编程。"},
    ]))

封装的好处:参数默认值统一(所有调用都是 0.3),截断警告自动打,换模型只改一处。

四、一个综合实验:观察” 规矩” 的服从度

测试 system prompt 在长对话中会不会失效:

system = "你回答任何问题都只能用'是'或'否'。"
history = [{"role": "system", "content": system}]

for q in ["Python是编程语言吗?", "天空是蓝的吗?", "详细介绍一下Python的历史",          "详细讲讲图灵的故事"]:
    history.append({"role": "user", "content": q})
    a = chat(history, temperature=0)
    history.append({"role": "assistant", "content": a})
    print(f"Q: {q}\nA: {a[:50]}\n")

典型现象:前几轮严格答” 是 / 否”,到后面开始” 越狱” 变长篇大论。结论:system prompt 不是硬约束,是软引导,上下文越长服从度越低。两个对策:

  1. 重要格式要求在最后一轮 user 消息里再强调一次(末尾指令服从度最高)
  2. 输出必须严格结构化时,用代码解析 + 失败重试兜底,而不是相信 prompt(第 15 篇讲 OutputParser 的动机就在这)

五、小结

  1. system prompt 四块结构:角色 → 规则 → 格式 → 边界;写正向可执行的规则
  2. temperature 0~0.3 用于 Agent;实验证明 0 确定性强
  3. max_tokens 是护栏,检查 finish_reason 判断截断
  4. 模型分档混用:便宜模型干杂活,推理模型干难活
  5. system prompt 会随对话变长而失效 —— 重要约束要末尾重申 + 代码兜底

六、自测

  • ☐ 用四块结构给” 外卖推荐助手” 写一个 system prompt
  • ☐ 跑 temperature 实验,亲眼看到 0 和 1.5 的差别
  • ☐ 把 max_tokens 设成 20 问一个长问题,观察 finish_reason

下一篇:[04 流式输出] —— AI 是一个字一个字想出来的,这个特性怎么用代码接住。

写在最后

如果想系统、完整地吃透 Harness、Hermes 整套前沿智能体开发体系,完成从只会调模型到可控、高质量、可落地的 AI 工程交付进阶,可以关注慕课网近期上新的《Harness&Hermes 多智能体开发特训营》