简介:《Developing Apps with GPT-4 and ChatGPT》是 O'Reilly 于2024年出版的英文技术读物,面向具备一定编程基础、希望将大模型能力落地到实际产品的开发者与AI应用学习者,重点填补从API调用到完整应用搭建之间的知识断层。全书从GPT-4与ChatGPT的基础能力讲起,覆盖提示工程、上下文与参数控制、函数调用、嵌入与向量检索、LangChain等框架的使用,并以智能聊天机器人、内容生成器等案例串联需求拆解、数据组织与效果评估的完整流程,对微调、成本与延迟的权衡亦有涉及。该版本为 O'Reilly Early Release,正文保留作者写作过程中的原始表述,并说明配套 GitHub 代码仓库将在后续开放,适合希望尽早跟进技术细节的读者。资源包内含1个PDF文件,大小约2.86MB,单文件承载全书正文,便于离线阅读与批注;目前已吸引1133人学习,可作为ChatGPT应用开发方向的入门至进阶参考,也适合技术选型时按章节快速查阅。
1. 先弄明白一件事:GPT-4 和 ChatGPT 到底是个什么东西
我第一次跟朋友说想学 AI ChatGPT 应用开发时,对方的反应是:「那不就是打开网页打字吗,有啥好学的?」这个误解特别常见。GPT-4 和 ChatGPT 这类大语言模型,本质是一台文字接龙机器:你给它一段话,它一个词一个词往下猜,猜得通顺得吓人。网页聊天框只是它的展示窗口,真正值钱的部分在窗口后面——你可以让一段程序直接调用它,把这种「接龙」能力嵌进自己的产品里。这份资料讲的就是后面这件事:怎么从「会用网页」变成「会做东西」。它适合完全没写过 AI 应用、但能看懂一点点代码的人,更适合那些用聊天框时总想「这要是能自动跑就好了」的人。
2. 为什么值得学:从「会聊天」到「能做出东西」中间隔着什么
2.1 一个类比:模型是发动机,应用是车
很多人第一次听到「开发 AI 应用」,脑子里浮现的是造一台发动机。其实不是。模型提供方已经把发动机造好了,你买到的是一个接口——把一段文字发过去,它把下一段文字送回来。就这么朴素,别被「人工智能」四个字唬住。
真正难的是后面的活:你要给这台发动机装底盘、接方向盘、配油箱、做刹车。底盘就是界面和交互,方向盘就是提示词设计,油箱就是成本和速度,刹车就是内容安全和出错处理。装好了,它就是一辆能开的车;装不好,就是一堆零件在空转。
这份资料的价值在于,它没有一上来就让你啃数学公式,而是先告诉你这台发动机的工作方式,再一段一段教你把车拼出来。我翻的时候最大的感受是:它把「调用一次模型」这件看起来玄学的事,拆成了看得见的几个动作。
2.2 这份资料顺着一条什么线往下铺
整份资料的主线很清楚,从最基础的概念往里走,再一层层往外扩成能上线的系统。
最前面讲的是基础:什么是自然语言处理,模型是怎么从早期的词频统计一路进化到今天的架构,为什么「注意力机制」这类设计让模型能读懂上下文,以及你发一句话过去之后,它在内部经历了什么。这一段看着像原理,其实是最省钱的一段——懂了它,后面调参数、排查问题心里就有底。
接着进入能动手的部分:怎么发起第一次调用,怎么设计提示词让输出稳定,怎么把多轮对话串起来,怎么用框架把模型和外部数据、外部工具接上,让它不光会说,还能查、能算、能动手。再往后是微调和部署,以及成本、延迟、安全这些上线才暴露的问题。
我建议的读法是:前三章慢一点,把概念嚼烂;从有代码的那一章开始,每个例子都亲手敲一遍,别复制粘贴。复制粘贴跑通的是别人的代码,手敲跑通的才是你自己的。
2.3 学完能做什么:四个马上想得到的场景
不吹,说点实际的。
第一个是客服类。把常见问题、产品文档整理成一段背景资料,每次用户提问时一起发给模型,让它基于资料回答。这件事的门槛低到一个人一个下午就能做出个能用的雏形。
第二个是内容生成。批量写商品描述、批量改写成不同语气、批量起标题。这类场景对「绝对准确」要求不高,对「稳定可控」要求高,正好是提示词工程能发挥的地方。
第三个是知识问答。把一堆散落在文档里的东西变成一个能对话的入口。难点不在模型,在你怎么把文档切块、怎么检索、怎么把检索结果拼进提示里——这也是资料里会讲到的部分。
第四个是内部小工具。比如自动整理会议纪要、自动给一批数据打标签、自动把一封长邮件压成三行。这类需求公司内部遍地都是,做出来立刻有人用。
2.4 说句实在话:它不能帮你解决什么
有些期待要提前打掉。模型不是一个能自己思考的系统,它没有记忆,不会主动记事,你上一次跟它说的话,下一次它不记得。它也不知道今天发生了什么,除非你把信息塞给它。它甚至会一本正经地编造不存在的东西,而且语气非常自信——这一点在你做出第一个能跑的例子之后,很快就会撞上。
所以「学它」不等于「学会一个万能工具」,而是学会一套工程习惯:怎么给足信息、怎么设好边界、怎么验证输出、怎么在它出错时兜住。这套习惯,才是这份资料真正想教的东西。
3. 三个必须补的地基概念:token、提示词和上下文窗口
3.1 token:模型眼里的字不是字
你看到的是句子,模型看到的是 token。token 不是字,也不是词,是介于两者之间的碎片。英文里常见词可能整个是一个 token,生僻词会被拆成好几块;中文里一个字常常对应一到两个 token 不等。
举个例子,句子The weather is nice today, so I decided to在模型眼里大概会被切成["The", "wea", "ther", "is", "nice", "today", ",", "so", "I", "de", "ci", "ded", "to"]。注意weather被劈成了两半,decided被劈成了三块。模型就是拿着这样一串碎片在做接龙。
这个冷知识有三个实际用途。一是算钱:绝大多数平台按 token 计费,输入和输出分开算,你写一个长提示词,成本就是按 token 累积的。二是估算长度:英文的经验值是 100 个 token 约等于 75 个单词,中文更费,一个字常常顶一到两个 token。三是判断为什么会截断:模型的输出长度是按 token 限制的,不是按字数,你以为只写了三百字,可能已经跑到上限了。
提示:中文内容的 token 消耗通常比同意思的英文高不少,做成本预估时别拿英文的数字直接套。
3.2 提示词:你跟模型说话的方式决定了一半结果
提示词就是你发过去的那段话。听起来简单,但新手和老手做出来的效果能差出好几条街。
原因在于模型的工作方式:它根据你给的全部文字,预测下一个最可能的 token,一个接一个往下推。你给的信息越模糊,它能猜的方向就越多,输出就越飘。这不是它不听话,是你没把范围收窄。
一个能立刻见效的写法是分三层。第一层给角色和边界:你是谁、你要做什么、你不要做什么。第二层给材料:把事实、文档、数据直接贴进去,别让它自己去想。第三层给格式:用几段、要不要列表、要不要 JSON、长度控制在多少。三层写完,输出稳定性会上一个台阶。
还有一个新手常忽略的点:给例子比给形容词管用。「语气专业一点」是形容词,模型各有各的理解;给一段你满意的示范,说「照这个风格写」,效果立刻不一样。
3.3 上下文窗口:模型的短期记忆有多长
上下文窗口指的是模型一次能「看见」多少 token。它包含你发过去的全部内容加上它生成的回复。不同版本这个数字差别很大,有的支持八千多 token,有的能到三万两千多。
这个数字为什么重要?因为它决定了你的应用能装多少东西。你要做文档问答,一份资料就两万字,那它一次根本塞不进去,你必须先切块、先检索、只把最相关的几段放进去。你要做多轮对话,聊到第二十轮的时候,前面的内容可能已经被挤出去了,模型就「忘了」你开头说过什么。
我第一次做多轮对话时就翻过这个车:前几轮聊得好好的,到第七八轮突然开始答非所问。查了半天代码,最后发现是历史消息越堆越长,把窗口撑爆了,最早的系统提示被挤掉了。解决办法也简单,超过一定轮数就做摘要,或者只保留最近若干轮加一句总的背景说明。
3.4 一个小实验:亲手估一估你的一段话有多少 token
不用装任何库,写个粗糙的估算函数就能建立手感:
def rough_tokens(text: str) -> int: """极粗略估算 token 数,只用来建立手感,不能当计费依据""" # 统计中文字符数量 chinese = sum(1 for ch in text if '\u4e00' <= ch <= '\u9fff') # 剩下的按英文/符号处理 others = len(text) - chinese # 中文按 1 字约 1.5 token,英文按 4 字符约 1 token return int(chinese * 1.5 + others / 4) print(rough_tokens("帮我写一段一百字左右的产品介绍,语气要克制")) print(rough_tokens("Write a short product description in a calm tone."))这个函数的意义不是精确,而是让你在写提示词之前有个数:这句话大概要花多少、还能塞多少资料进去、输出留多少空间。跑几次你就会发现,中文确实更「贵」,加一段两千字的产品文档进去,成本比想象中高。
4. 发出第一个请求:几行代码跑通你的第一次模型调用
4.1 开工前要准备的三样东西
第一样是账号和密钥。在模型提供方的平台上注册,进控制台创建一个 API Key。这个字符串就是你调用的凭证,长得像一串随机字符。它等同于密码,泄露了别人就能拿你的额度跑东西。
第二样是一个能发网络请求的环境。会 Python 最好,装个解释器加一个 requests 库就行;不会 Python 也没关系,任何能发 HTTP 请求的语言都可以,原理完全一样。
第三样是平台文档里的接口地址和模型名。地址和模型名会变,别抄网上的老文章,去官方文档现查。这份资料成书较早,具体的模型名和地址请以你手上的文档为准。
# 安装发请求用的库,只需要这一个 pip install requests4.2 把密钥藏进环境变量,别写进代码
这一步九成新手会跳过,然后在一周后后悔。把密钥直接写在代码里,你一旦把代码发给别人、传到公开仓库、截图发群,它就不再是秘密了。
正确的做法是放进环境变量:
# macOS / Linux:临时生效,关掉终端就没了 export MODEL_API_KEY="你的密钥粘贴在这里" # Windows PowerShell $env:MODEL_API_KEY="你的密钥粘贴在这里"代码里用os.environ.get("MODEL_API_KEY")去读。这样代码本身不含任何秘密,可以放心分享、可以提交到代码托管平台,密钥由运行环境提供。我现在的习惯是:任何跟密钥有关的代码,先写读取环境变量的那行,再写别的。
4.3 第一个请求:把一句话发出去,拿一句话回来
下面这段是能直接跑的最小例子。我特意用最原始的 HTTP 请求方式,而不是某个封装好的库,因为这样你能看清发出去的到底是什么。
import os import requests # 1. 从环境变量读密钥,代码里不出现明文 api_key = os.environ.get("MODEL_API_KEY") if not api_key: raise SystemExit("没读到密钥,先设置 MODEL_API_KEY 环境变量") # 2. 接口地址和模型名,按平台文档当前的写法填 url = "https://api.example.com/v1/chat/completions" # 3. 请求体:模型名、消息列表、几个关键参数 payload = { "model": "your-model-name", "messages": [ {"role": "system", "content": "你是一个说话简洁的技术助手"}, {"role": "user", "content": "用两句话解释什么是大语言模型"} ], "temperature": 0.7, # 0 最保守,1 最发散,新手先给 0.3 更稳 "max_tokens": 200 # 给回复设个上限,省钱也防跑飞 } # 4. 请求头:认证信息 + 声明发的是 JSON headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } # 5. 发出去,加超时,别让程序卡死 resp = requests.post(url, json=payload, headers=headers, timeout=30) # 6. 先看状态码,再看内容 print("状态码:", resp.status_code) print("完整返回:", resp.json())逐段说清楚。第 1 段读密钥,如果没读到就直接退出,比让它带着None去发请求、然后收到一个莫名其妙的 401 要好排查得多。第 3 段的messages是核心结构,它是一个列表,每一项有两个字段:role表示说话人身份,content表示说的内容。system是给模型的总体设定,只在开头出现一次;user是你说的话;后面讲到多轮对话时会再加一个assistant,表示模型自己说过的话。
temperature控制随机的程度。给 0,模型每次都挑最可能的那个词,输出稳定但可能呆板;给 1,它会更愿意挑次可能的词,输出花样多但更容易跑偏。做客服、做数据抽取这类要稳定的场景,先给 0 到 0.3;做创意文案再往上调。
max_tokens限制的是回复的长度上限,不是输入。设小了回复会被硬生生切断,设大了流量和成本都上去。新手常见的问题是设了个 50,然后抱怨模型说话说一半,其实这是自己设定的。
4.4 读懂返回结果里的每个字段
一次成功的调用,返回的大致长这样:
{ "id": "chatcmpl-abc123", "object": "chat.completion", "created": 1700000000, "model": "your-model-name", "choices": [ { "index": 0, "message": {"role": "assistant", "content": "大语言模型是一种在海量文本上训练出来的模型……"}, "finish_reason": "stop" } ], "usage": {"prompt_tokens": 28, "completion_tokens": 62, "total_tokens": 90} }choices是结果列表,一般只有一项,取choices[0]["message"]["content"]就是你想要的那段文字。finish_reason是关键字段:值是stop说明正常说完了;是length说明撞到了max_tokens上限被切断,这时候你要么调大上限,要么在提示词里要求它简短;如果出现别的原因,通常意味着内容被安全策略拦下了。
usage那三个数字是计费依据,prompt_tokens是你发过去的量,completion_tokens是它生成的量。多留意这两个数,你对成本的感觉会越来越准。
4.5 从一次性调用改成多轮对话
第一次跑通之后,下一步就是让它记住上下文。原理很朴素:模型没有记忆,所谓「记住」,是你每次把之前的对话一起再发一遍。
# 用一个列表维护对话历史,这就是全部的“记忆” history = [{"role": "system", "content": "你是一个说话简洁的技术助手"}] def chat(user_input: str) -> str: # 1. 把用户这句话追加进历史 history.append({"role": "user", "content": user_input}) payload = { "model": "your-model-name", "messages": history, # 整个历史一起发过去 "temperature": 0.3 } resp = requests.post(url, json=payload, headers=headers, timeout=30) reply = resp.json()["choices"][0]["message"]["content"] # 2. 把模型的回复也追加进历史,下一轮才“记得” history.append({"role": "assistant", "content": reply}) return reply print(chat("我在做一个客服机器人,应该注意什么?")) print(chat("刚才说的第一点,展开讲讲"))第二句能接上第一句,靠的就是history这个列表被完整地又发了一遍。理解这一点,你就理解了市面上绝大多数聊天产品的底层逻辑。
但也正因为如此,历史会越滚越长,token 消耗会翻倍增长,最终撞上上下文窗口。可以在历史超过若干轮时做一次压缩:让模型把前面的对话总结成一百字,用这条总结替换掉那一大段历史。
5. 新手最容易翻车的五个地方:报错、截断和胡说八道怎么排查
5.1 坑一:401 报错,说认证失败
现象:程序一跑就返回 401 或者类似的认证错误,代码看起来没毛病。
原因:八成是密钥没读到。环境变量是在某个终端窗口里设的,你换了个窗口、换了个编辑器、或者用图形界面启动程序,那个变量就不存在了。少数情况是密钥复制时多带了空格或换行,或者这个密钥已经被禁用。
解决:先在程序里把读到的密钥长度打印出来(只打印长度,别打印内容),长度不对就是没读到。然后确认设置变量和运行程序是在同一个终端里。最后检查复制时有没有带进多余字符。
5.2 坑二:回复说了半句就没了
现象:输出的最后一句停在半截,标点都没有。
原因:撞上了max_tokens。这不是模型坏了,是它被你的设定掐断了。中文尤其容易中招,因为同样的意思,中文消耗的 token 更多。
解决:先看返回里的finish_reason,如果是length,那就把上限调大。同时可以在提示词里明确「请在一百字内回答」,让它在预算内把话说完整,比事后补救更省事。
5.3 坑三:它一本正经地编造不存在的东西
现象:你问一个具体的事实,它给出一个语法完美、细节丰富、但完全错误的答案。这个现象在行业里叫「幻觉」,几乎每个新手第一次撞上都会愣一下。
原因:模型的本质是预测下一个最可能的词,不是查数据库。它没有「我不知道」这个默认档位,只要语句通顺,它就会顺着往下写。你越问得具体、越涉及冷门细节,它编得越像真的。
解决:三条一起上。一是把事实材料直接贴进提示词,明说「只根据以下资料回答,资料里没有就说不知道」。二是把温度调低。三是凡是关键结论,都要求它给出依据或原文引用,方便你复核。我现在的习惯是:任何要对外发布的内容,模型写的部分一律人工过一遍。
5.4 坑四:同样的提示词,每次结果都不一样
现象:同一段提示词跑十次,输出十个版本,有时候差别还挺大。
原因:这是设计如此。只要温度不是 0,模型就是在按概率抽样,每次抽到的可能不同。看起来像玄学,其实是随机性在起作用。
解决:要稳定就把温度设成 0,并固定其他参数。但要注意,即使这样也不保证百分之百一致,服务端的实现细节可能带来微小差异。真正要稳定,靠的是结构化输出:要求它返回固定格式的 JSON,然后你自己写校验逻辑,不合格就重试。这一步是新手到能用的分水岭。
5.5 坑五:跑了一晚上,账单吓一跳
现象:测试阶段随便跑,某天打开后台发现用量比预期高一大截。
原因:通常有三个。一是把整段历史每轮都重发,聊得越久越贵;二是提示词里贴了大量文档,每次调用都重贴一遍;三是循环里没有退出条件,出了错还在反复重试。
解决:给历史做摘要或截断;把固定的长文档放进缓存机制或者改成检索后只贴相关段落;给重试加次数上限和退避等待。这三个动作做完,成本通常能降下来一大截。
注意:测试阶段就把用量监控打开,别等到账单出来才发现问题。这不是省钱技巧,是基本工程习惯。
6. 下一步学什么:一条不绕路的四周入门路线
跑通第一个请求之后,很多人会陷入一种状态:例子会跑了,但不知道自己下一步该干嘛,于是开始到处收集教程,收藏夹越来越满,动手越来越少。我给个具体一点的走法。
| 阶段 | 时间 | 具体做什么 | 判断是否过关 |
|---|---|---|---|
| 打地基 | 第 1 周 | 把 token、上下文窗口、温度这几个概念用自己的话讲一遍;手敲第一个请求和返回解析 | 能解释「为什么回复会被截断」 |
| 练提示 | 第 2 周 | 挑一个真实小需求,比如把一段产品介绍改成三种语气,反复调到稳定 | 同样输入,十次里有八次符合预期 |
| 搭闭环 | 第 3 周 | 做一个带界面的小工具,接入你自己的资料,处理报错和空结果 | 别人能用,出错不会崩 |
| 上强度 | 第 4 周 | 加结构化输出校验、加历史摘要、加用量统计 | 能说清每次调用的成本和失败率 |
关于顺序,我有个血泪经验:别在第二周就急着学框架。框架是为了解决「你已经有的问题」而存在的,你还没做过三个以上的例子,根本不知道自己有什么问题,学框架只会学成一堆配置项的背诵。先把裸调用写熟,写到闭着眼睛能写出消息列表,再去看框架,你会发现它做的事情你全都认识。
还有一件事值得早点做:给自己建一个提示词笔记本。每次调出一个好用的版本,连同当时的输入、输出、参数一起存下来。我一开始嫌麻烦不记,后来做第二个项目时想复用第一个项目里的写法,翻了半天聊天记录才找回来。从那以后我每次调通一个提示词,都强制走一遍「存原文、存参数、存一条示例输出」的流程,花不了两分钟,省下的是成小时的重来。
最后一个建议,是尽快让一个真实的人用上你做的东西,哪怕只是同事。自己测的时候人会自动避开边界情况,别人一上手,三分钟内就能把你没想到的坑踩出来。
希望帮到你。
本文还有配套的精品资源,点击获取