# 零基础复现Claude Code(二):地基篇——让模型开口说话

开篇:同样是调用API,为什么结果天差地别?

你肯定用过ChatGPT或Claude的网页版,也可能调用过几次API。但你有没有想过一个问题:

同样是调用gpt-4模型,为什么:

  • 你在ChatGPT网页上问"帮我写代码",它会给你一段代码 + 详细解释
  • 但如果你用API调用,只发送{"role": "user", "content": "帮我写代码"},它可能回复"好的,请告诉我你想写什么代码"

区别在哪?

答案藏在一个你看不见的地方:System Prompt(系统提示词)

💡 回到第一篇的"实习生"比喻 :System Prompt就是实习生的岗位说明书

ChatGPT网页版在你看不见的地方,已经偷偷给实习生塞了一份说明书,上面写着:

  • 你是一个有用的助手
  • 你应该提供详细的解释
  • 你应该用Markdown格式输出代码

而你直接调用API时,实习生没有拿到岗位说明书,它站在你面前,一脸茫然地问:"老板,我该干什么?"

这一篇,我们就要揭开这个"看不见的开关",并且亲手写一个能让模型"听话干活"的System Prompt。

本节目标

读完这篇文章,你将:

  • 理解System Prompt的本质:它不是"礼貌用语",而是控制模型行为的核心机制
  • 掌握API调用的完整流程:从环境变量到消息格式,写出第一个能跑通的LLM调用
  • 学会设计Agent专用的System Prompt:让模型输出结构化的Thought和Action,而不是随意聊天

原理深潜:模型输出的"配方"

在动手写代码前,我们先理解一个核心公式。

公式:模型输出是什么决定的?

scss 复制代码
模型输出 = f(System Prompt, 对话历史, 温度参数, ...)

用人话翻译:

  • System Prompt:告诉模型"你是谁,该怎么做"(角色设定)
  • 对话历史:之前的所有对话内容(上下文)
  • 温度参数:控制输出的随机性(0=确定性,1=创造性)

关键洞察 :System Prompt是这个公式里唯一一个你可以完全控制、且对所有对话都生效的变量

📍 与第一篇公式的衔接

回忆第一篇文章中我们建立的ReAct公式:

ini 复制代码
初始状态 S_0 = (用户指令, 空对话历史)

循环 t = 0, 1, 2, ...:
    Thought_t, Action_t = LLM(S_t)  ← 📍 本篇解决的就是这部分!
    Observation_t = Execute(Action_t)
    S_{t+1} = S_t + (Thought_t, Action_t, Observation_t)

这一篇聚焦于LLM(S_t):如何让模型根据当前状态,输出我们想要的格式(Thought + Action)。

System Prompt就是控制这个LLM(S_t)输出的"旋钮"------它告诉模型:

  • 你应该先思考(输出Thought)
  • 再给出行动(输出Action)
  • 格式必须是:Thought: ... \n Action: ...

下一篇我们会实现Execute(Action_t),把Action真正执行。

类比:System Prompt是"职业基因"

回到我们的"实习生工具箱"类比:

  • 没有System Prompt的模型 = 一个没有岗位说明书的实习生,你每次都要重复告诉他"你是工程师,要写代码"
  • 有System Prompt的模型 = 一个拿到了岗位说明书的实习生,他知道自己的职责,你只需要说"帮我修Bug",他就知道该怎么做

对比实验:同样的问题,不同的System Prompt

让我们看一个真实的对比:

实验A:普通聊天助手

python 复制代码
System Prompt: "你是一个有用的助手。"
User: "怎么读取文件?"
Model: "读取文件有很多方法,你想用哪种编程语言呢?Python的话可以用open()函数..."

实验B:工程师Agent

python 复制代码
System Prompt: """你是一个Python工程师Agent。
当用户提问时,你应该:
1. 先思考(输出Thought)
2. 再给出代码(输出Action)
输出格式:
Thought: [你的思考过程]
Action: [具体的代码]
"""
User: "怎么读取文件?"
Model: """
Thought: 用户想知道如何读取文件,我应该给出Python的标准做法,使用with语句确保文件正确关闭。
Action:
```python
with open('file.txt', 'r') as f:
    content = f.read()

"""

markdown 复制代码
**看到区别了吗?**
- 实验A:模型在"聊天",输出是自然语言对话
- 实验B:模型在"工作",输出是结构化的思考+代码

这就是System Prompt的威力------**它决定了模型的"工作模式"**。

## 动手实操:封装你的第一个LLMClient

现在我们开始写代码。目标是封装一个极简的`LLMClient`类,能够:
1. 读取API Key(从环境变量)
2. 发送消息给模型
3. 返回模型的回复

### 第一步:安装依赖和设置API Key

```bash
pip install openai

然后设置环境变量(千万别硬编码在代码里!):

bash 复制代码
# Linux/Mac
export OPENAI_API_KEY="sk-..."

# Windows PowerShell
$env:OPENAI_API_KEY="sk-..."

# 或者用.env文件(推荐)
echo 'OPENAI_API_KEY=sk-...' > .env

第二步:编写LLMClient类(约20行)

创建一个文件llm_client.py

python 复制代码
import os
from openai import OpenAI

class LLMClient:
    """极简的LLM客户端,封装API调用"""
    
    def __init__(self, model="gpt-4"):
        # 🔑 从环境变量读取API Key,避免硬编码
        api_key = os.getenv("OPENAI_API_KEY")
        if not api_key:
            raise ValueError("请设置环境变量 OPENAI_API_KEY")
        
        self.client = OpenAI(api_key=api_key)
        self.model = model
    
    def chat(self, messages):
        """
        发送消息给模型,返回回复内容
        
        参数:
            messages: 消息列表,格式为 [{"role": "system/user/assistant", "content": "..."}]
        
        返回:
            模型的回复文本
        """
        # 🔑 核心:messages是一个消息列表,每条消息有role(角色)和content(内容)
        # role有三种:
        # - "system":系统提示词,设定模型的行为规则
        # - "user":用户说的话
        # - "assistant":模型之前的回复(用于多轮对话)
        # 
        # 注意:messages是一个列表,模型会按顺序读取,
        # 所以顺序很重要!System Prompt应该放在第一条。
        response = self.client.chat.completions.create(
            model=self.model,
            messages=messages,
            temperature=0.7  # 0=确定输出(适合代码),1=随机输出(适合创意)
        )
        
        # 🔑 提取模型回复的文本内容
        return response.choices[0].message.content

代码解读

  • __init__:初始化时读取API Key,如果没有就报错(避免运行到一半才发现)
  • chat:核心方法,接收消息列表,返回模型回复
  • messages:这是OpenAI API的标准格式,每条消息都有role(角色)和content(内容)

第三步:第一次调用------让模型自我介绍

创建一个测试文件test_basic.py

python 复制代码
from llm_client import LLMClient

# 创建客户端
client = LLMClient(model="gpt-4")

# 🔑 构造消息列表
messages = [
    {
        "role": "system",
        "content": "你是一个Python专家,回答要简洁专业。"
    },
    {
        "role": "user",
        "content": "用一句话介绍你自己。"
    }
]

# 调用模型
response = client.chat(messages)
print("模型回复:")
print(response)

运行这段代码,你会看到类似这样的输出:

复制代码
模型回复:
我是一个Python专家,专注于提供简洁、专业的技术解答和代码实现。

恭喜!你已经成功调用了LLM API。

第四步:对比实验------System Prompt的威力

现在我们做一个对比实验,验证System Prompt的作用。创建test_comparison.py

python 复制代码
from llm_client import LLMClient

client = LLMClient(model="gpt-4")

# 实验A:普通助手
print("=" * 50)
print("实验A:普通助手")
print("=" * 50)
messages_a = [
    {"role": "system", "content": "你是一个有用的助手。"},
    {"role": "user", "content": "怎么读取文件?"}
]
response_a = client.chat(messages_a)
print(response_a)

# 实验B:工程师Agent
print("\n" + "=" * 50)
print("实验B:工程师Agent")
print("=" * 50)
messages_b = [
    {
        "role": "system",
        "content": """你是一个Python工程师Agent。
当用户提问时,你应该:
1. 先思考(输出Thought)
2. 再给出代码(输出Action)

输出格式:
Thought: [你的思考过程]
Action: [具体的代码]
"""
    },
    {"role": "user", "content": "怎么读取文件?"}
]
response_b = client.chat(messages_b)
print(response_b)

运行后你会看到明显的差异:

实验A可能输出:

scss 复制代码
读取文件有多种方法,最常用的是使用Python的open()函数。你可以这样做:
[一段代码 + 详细解释]

实验B可能输出:

csharp 复制代码
Thought: 用户需要读取文件的代码示例,我应该给出使用with语句的标准做法。
Action:
with open('file.txt', 'r') as f:
    content = f.read()

看到了吗?同样的问题,不同的System Prompt,输出的结构完全不同。

与真实代码的对照

在真实的Claude Code实现中(rust版本),这部分对应的是:

我们的实现 真实代码位置 关键差异
LLMClient.chat() crates/api/src/client.rsProviderClient trait 真实版支持流式输出、多Provider
messages 格式 crates/api/src/types.rs 的消息结构体 真实版有更多字段(stop、top_p等)
System Prompt crates/runtime/src/prompt.rs中动态生成 真实版长达数百行,包含工具定义

想深入研究的读者

  • 打开crates/api/src/client.rs,搜索trait ProviderClient,你会看到流式处理的完整逻辑
  • 打开crates/tools/src/lib.rs,可以看到工具注册和执行的机制

为什么我们的实现这么简单?

功能 我们的简化版 真实版 简化原因
流式输出 ❌ 无 ✅ 有 教学版不需要实时体验
多Provider ❌ 只支持OpenAI ✅ 支持多个 一个够理解原理
错误重试 ❌ 无 ✅ 有 先跑通主流程
Token管理 ❌ 无 ✅ 精确计数 第7篇会涉及

记住我们的目标:理解齿轮怎么转,而不是造一辆完整的车。

真实代码还包括:

  • 流式输出(逐字返回,而不是等全部生成完)
  • 多Provider支持(OpenAI、Anthropic、本地模型)
  • 错误重试机制
  • Token计数和预算管理

但核心逻辑是一样的:构造消息列表 → 调用API → 返回结果

System Prompt设计的3个原则

通过上面的实验,我们总结出设计Agent专用System Prompt的3个原则:

原则1:明确角色定位

❌ 不好的写法:

复制代码
你是一个助手。

✅ 好的写法:

复制代码
你是一个Python工程师Agent,专门帮助用户修复代码Bug、编写测试、优化性能。

为什么? 明确的角色定位让模型知道自己的"专业领域",避免答非所问。

原则2:规定输出格式

❌ 不好的写法:

复制代码
你应该帮助用户解决问题。

✅ 好的写法:

ini 复制代码
你的输出格式必须是:
Thought: [你的思考过程]
Action: [具体的操作]

为什么? 结构化的输出格式让我们能够解析模型的回复,提取出"思考"和"行动"两部分。这是ReAct循环的基础。

原则3:给出具体示例

❌ 不好的写法:

复制代码
你应该先思考再行动。

✅ 好的写法:

vbnet 复制代码
示例:
User: 帮我读取main.py文件
Assistant:
Thought: 用户想看main.py的内容,我应该使用read_file工具。
Action: read_file('main.py')

为什么? 具体示例是"Few-shot Learning"的体现,让模型通过例子理解你的期望。

⚠️ 新手容易踩的坑

  1. 坑1:API Key硬编码在代码里

    • 后果:代码一旦上传到GitHub,Key就泄露了,可能被盗刷
    • 正确做法:用环境变量或.env文件
  2. 坑2:忘记设置System Prompt

    • 后果:模型不知道自己该扮演什么角色,输出不可控
    • 正确做法:每次调用都要包含System Prompt
  3. 坑3:System Prompt写得太模糊

    • 错误示例:"你是一个好助手"
    • 后果:模型不知道"好"的标准是什么
    • 正确做法:具体描述角色、输出格式、工作方式
  4. 坑4:温度参数设置不当

    • temperature=0:输出非常确定,适合代码生成
    • temperature=1:输出很随机,适合创意写作
    • Agent通常用0.3-0.7之间

下一步:从"说话"到"行动"

现在你已经学会了:

  • 封装LLM API调用
  • 设计System Prompt让模型输出结构化内容
  • 理解System Prompt对模型行为的控制力

但有一个关键问题还没解决:

模型现在只会"说"(输出Thought和Action的文本),但不会"做"(真正执行Action)。

比如,模型输出了:

vbnet 复制代码
Thought: 我需要读取main.py
Action: read_file('main.py')

但这只是一段文本,文件并没有真的被读取。

下一篇,我们将实现ReAct循环的骨架------让模型能够:

  1. 输出Action
  2. 我们解析这个Action
  3. 执行对应的工具
  4. 把结果返回给模型
  5. 模型根据结果继续思考

这就是Agent从"聊天机器人"进化到"能干活的助手"的关键一步。

预告一个核心问题 :模型输出的是自然语言文本"read_file('main.py')",我们怎么把它转换成真正的Python函数调用?答案在下一篇揭晓。

📝 自检清单(读完本篇请确认)

在进入下一篇之前,请确认你能回答以下问题:

  • System Prompt和User Prompt的本质区别是什么?
  • 为什么messages列表中System Prompt应该放在第一条?
  • temperature=0和temperature=1分别适合什么场景?
  • 你能写出一个让模型输出"Thought: ... \n Action: ..."格式的System Prompt吗?

如果都能回答,恭喜你,Agent的"大脑"部分你已经掌握了。下一篇见!


系列进度

  • ✅ 第1篇:总览与前置准备------Claude Code到底是什么?
  • ✅ 第2篇:地基篇------让模型开口说话(System Prompt的艺术)
  • ⏭️ 第3篇:灵魂篇------ReAct循环的骨架
  • 第4篇:双手篇------赋予读写文件的能力
  • 第5篇:终端篇------赋予执行命令的超能力
  • 第6篇:整合篇------组装Mini Claude Code
  • 第7篇:上下文篇------让Agent看懂整个文件夹
  • 第8篇:反思与展望------我们得到了什么,还缺什么?
相关推荐
科技AI训练师2 小时前
2026 屋顶风机行业观察测评:英飞风机助力建筑通风排烟升级
大数据·人工智能
扬帆破浪2 小时前
免费开源的WPS AI插件 察元AI助手:脱密加密模块:Web Crypto 与口令校验
人工智能·开源·ai编程·wps
heimeiyingwang2 小时前
【架构实战】容器安全最佳实践
安全·架构
openFuyao2 小时前
openFuyao技术讲堂 | AI推理鹰眼(Eagle Eye)
人工智能
水木流年追梦2 小时前
CodeTop Top 300 热门题目2-最长回文子串
开发语言·人工智能·python·算法·leetcode
人工智能AI技术2 小时前
缓存基础知识:缓存策略、过期、击穿与雪崩
人工智能
生信之灵2 小时前
拓扑与曲率双剑合璧:scGeom如何从单细胞数据中“看见”细胞命运
人工智能·深度学习·算法·单细胞·多组学
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【33】Human-in-the-Loop(人在回路)演示
java·人工智能·spring
今天你TLE了吗2 小时前
LLM到Agent&RAG——AI概念概述 第五章:Skill
人工智能·笔记·后端·学习