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

零基础复现Claude Code(二):地基篇——让模型开口说话你肯定用过 ChatGPT 或 Claude 的网页版 也可能调用过几次 API 但你有没有想过一个问题 同样是调用 gpt 4 模型 为什么 你在 ChatGPT 网页上问 帮我写代码 它会给你一段代码 详细解释 但如果你用 API 调用 只发送 role user amp

大家好,我是讯享网,很高兴认识大家。这里提供最前沿的Ai技术和互联网信息。



你肯定用过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,而不是随意聊天

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

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

用人话翻译:

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

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

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

初始状态 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: ... Action: ...

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

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

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

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

实验A:普通聊天助手

 
  
    
    
System Prompt: "你是一个有用的助手。" 

User: "怎么读取文件?" Model: "读取文件有很多方法,你想用哪种编程语言呢?Python的话可以用open()函数…"

 

实验B:工程师Agent

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

"""

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

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

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

现在我们开始写代码。目标是封装一个极简的LLMClient类,能够:

  1. 读取API Key(从环境变量)
  2. 发送消息给模型
  3. 返回模型的回复

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

”`bash pip install openai

 

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

# Linux/Mac 

export OPENAI_API_KEY="sk-…"

Windows PowerShell

$env:OPENAI_API_KEY="sk-…"

或者用.env文件(推荐)

echo ‘OPENAI_API_KEY=sk-…’ > .env

 

创建一个文件llm_client.py

 
  
    
    
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

 
  
    
    
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的作用。创建test_comparison.py

 
  
    
    
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(" " + "=" * 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可能输出:

 
  
    
    
读取文件有多种方法,最常用的是使用Python的open()函数。你可以这样做: 

[一段代码 + 详细解释]

 

实验B可能输出:

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 → 返回结果

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

❌ 不好的写法:

 
  
    
    
你是一个助手。

✅ 好的写法:

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

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

❌ 不好的写法:

 
  
    
    
你应该帮助用户解决问题。

✅ 好的写法:

你的输出格式必须是: 

Thought: [你的思考过程] Action: [具体的操作]

 

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

❌ 不好的写法:

 
  
    
    
你应该先思考再行动。

✅ 好的写法:

示例: 

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

比如,模型输出了:

 
  
    
    
Thought: 我需要读取main.py 

Action: read_file(‘main.py’)

 

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

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

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

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

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

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

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


系列进度

  • ✅ 第1篇:总览与前置准备——Claude Code到底是什么?
  • ✅ 第2篇:地基篇——让模型开口说话(System Prompt的艺术)
  • ⏭️ 第3篇:灵魂篇——ReAct循环的骨架
  • 第4篇:双手篇——赋予读写文件的能力
  • 第5篇:终端篇——赋予执行命令的超能力
  • 第6篇:整合篇——组装Mini Claude Code
  • 第7篇:上下文篇——让Agent看懂整个文件夹
  • 第8篇:反思与展望——我们得到了什么,还缺什么?

小讯
上一篇 2026-04-26 20:02
下一篇 2026-04-26 20:00

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/280944.html