
day01把坑基本踩完了Python装好依赖装好API key申请下来也搞清楚了“大模型API到底是个什么东西”。但说实话那天的成就感很有限因为所有操作停留在“能调用”还没有真正把它变成“应用”。所以day02给自己定了三个目标第一彻底看懂一次完整的API请求和响应第二把多轮对话的状态管理搞清楚让程序拥有“记忆”第三让模型输出固定结构的数据而不是让人去解析它的口水话。顺便我也想把Agent这个概念往下拽一拽看看它和普通聊天到底差在哪为后面的AI应用开发学习路线做好铺垫。如果你也是刚开始学大模型应用开发这篇笔记应该能帮你少走不少弯路。1. 第二天到底该学什么1.1 为什么第二天要从“调通API”开始很多人学AI应用开发第一反应是去抄框架、跑步进Agent平台结果环境没配好、底层调用逻辑不懂跑两天就卡死在玄学报错里。我给自己定的顺序很简单先徒手调一次API再做工具最后碰框架。原因也很直白大模型应用开发的核心不是“写提示词”而是“用程序调度模型能力”这件事。如果连一次HTTP请求和响应都看不懂后面无论用LangChain、Coze还是别的工具出了问题都很难定位。day02的主要任务就是从零开始用手写代码调通大模型API跑通一个真正能完成任务的“小应用”。这个应用不需要多高级但必须包含三个要素有输入、有状态、有结构化输出。只要这三样齐全它就是一个可以嵌入业务系统的功能组件而不再是一个网页聊天框。1.2 三个目标对应应用开发的三个基本功我给day02拆了三个核心目标它们分别对应应用开发里绕不开的“输入”、“状态”和“输出”。第一个目标是理解请求与响应结构。你给模型发的消息、模型返回的内容、以及请求里带的各种参数到底是怎么一层层组织起来的。第二个目标是管理多轮对话上下文。聊天应用最大的陷阱就是“模型没有记忆”它只是把你每次发给它的全部历史消息都重新读一遍。如何组织和管理这些历史消息决定了你的应用是聪明还是智障。第三个目标是让模型输出固定结构的数据。聊天文本人能看懂但程序不能直接消费我们要让模型按照约定的JSON格式吐数据程序才能进一步处理。这三个目标不是随便定的它们对应了所有AI应用都躲不开的接口契约、状态管理和数据解析问题。把这三样练熟了后面的RAG、Agent、工作流编排都只是在这盘棋上往上叠功能。1.3 思维转变模型不是聊天框是功能组件学AI应用开发最怕的一件事情就是一直停留在“会写prompt、会抄SDK示例”这个层面。这种状态就像会开机不会装系统看起来用了电脑其实没掌握任何能解决复杂问题的能力。我的建议是把模型想成一个“外部服务”你给它下发任务描述和上下文它返回一段文本或结构化数据你的代码负责决定什么时候调用它、调用之后拿结果做什么。模型能力越强只是这个服务的智商越高但应用的主控权和数据流始终应该在你自己手里。这个思维转变搞不通后面学什么都容易跑偏。2. 核心细节API调用背后的底层逻辑2.1 messages里的三位主角system、user、assistant所有兼容OpenAI格式的API请求体里最核心的东西就是messages数组。这个数组里每一条规定了说话的角色和内容最常用的有三个角色。system是系统提示相当于给模型做岗前培训。你在这里设定它扮演什么角色、遵守什么规则、输出什么格式。比如“你是客服小A回答要简洁亲切不要超过30个字”。user是用户的输入就是每次用户真正说的话。assistant是模型的历史回复。在多轮对话里需要把之前模型的回复以assistant角色塞回messages里模型才能知道“它刚才已经说过什么”从而保持对话连贯。初学者最常见的错误有两个。一是以为system里的设定只在第一轮生效结果每轮都重新发一遍system导致上下文窗口被无谓占满。二是完全不保留历史消息每次请求只发当前这一句导致模型像金鱼一样只有三秒记忆。正确做法是第一轮发system加user模型返回assistant消息然后把这个assistant消息追加到消息列表第二轮再把新的user消息追加进去继续发给模型。如此往复消息列表就是你的对话记忆。2.2 参数里的门道temperature、max_tokens、top_p除了messages请求体里的参数也直接决定输出质量和成本。这里挑几个最常用也最容易被误解的说。temperature控制随机性取值范围一般是0到2。数值越小输出越确定、越保守适合代码生成、数据提取、分类这类要求稳定的任务数值越大输出越发散、越有创造性适合创意写作、头脑风暴。我自己的经验是做结构化提取的时候用0.2以下做聊天对话用0.7左右做文案创意可以到0.9甚至更高。注意temperature大不等于“聪明”它只是让模型更敢胡说很多时候反而会降低正确率。top_p是核采样作用类似temperature但机制不同。它把每个位置所有候选词按概率从高到低排列累计概率达到p就只从这些词里采样。通常保持默认值1就行不需要和temperature同时大幅调整否则容易让输出变得不可控。很多同学两个参数一起调来调去最后输出反而更差了。max_tokens限制模型生成的token数上限。这里有个容易踩的坑max_tokens不是限制“字数”而是限制“token数”。一个汉字在不同模型的tokenizer里大约对应0.6到2个token不等所以如果你把max_tokens设成100最终生成的中文可能只有几十个字就被硬生生截断了。因此给足余量很重要尤其当你让模型输出JSON时截断会导致解析失败。2.3 token、上下文窗口和计费被大多数人忽略的成本铁律token是模型处理文本的最小单位既不是字节也不是完整单词。英文里一个常见词通常是一个token中文里一个汉字约等于一个或多个token。API服务商按token数计费而且输入和输出的单价往往不一样。上下文窗口则是模型一次能处理的最大token数。你的system提示词、历史对话、用户输入、以及模型输出加起来都不能超过这个窗口。一旦超了API会直接报错。更麻烦的是窗口是并行的你在里面塞了太多历史消息留给模型思考和输出的空间就少了结果就是回答质量下降。举个具体例子假设一个模型的上下文窗口是8K token你设定了一个500 token的system提示词又累积了5轮每轮约1000 token的对话历史那还剩多少空间留给新问题和回答算一下就明白了消耗起来相当快。这也是为什么所有正经的聊天应用都要做历史消息截断或摘要因为不做的话会话只要长一点成本就会成倍上涨窗口也会顶爆。这个账越早算明白越好。3. 实操过程用Python把第一个AI应用跑起来3.1 最小API调用一次请求看清全貌day02的第一个实操不用任何SDK直接用requests发一次HTTP请求。原因是能让你彻底看清这个API的本质它就是一个普通的POST接口你给JSON它回JSON仅此而已。这里以常见的兼容OpenAI格式的API为例import requests url https://api.example.com/v1/chat/completions api_key sk-你的key payload { model: your-model-name, messages: [ {role: system, content: 你是一个乐于助人的AI助手。}, {role: user, content: 请用一句话解释什么是大模型API。} ], temperature: 0.7, max_tokens: 200 } headers { Authorization: fBearer {api_key}, Content-Type: application/json } resp requests.post(url, jsonpayload, headersheaders, timeout30) data resp.json() print(data[choices][0][message][content])这段代码跑通之后你可以在控制台把打印改成print(data)完整地看一看响应结构。注意返回的JSON里有几个关键字段choices是一个数组因为可以一次生成多个候选但默认是1个、choices[0].message.content就是生成的内容、usage字段会告诉你本次请求消耗了多少token。这是最直观的“解剖”方式。刚开始学的时候别急着往上叠功能先把这个最小调用跑通把状态码、响应结构、报错信息都亲眼见一遍后面遇到问题才有判断依据。3.2 从单轮到多轮消息列表就是记忆模型本身是无状态的它所谓的“记忆”完全靠你每次请求时把历史消息重新发给它。所以管理应用的状态本质就是管理messages列表。下面这段代码实现了一个简单客服机器人它会连续和用户对话每次对话都把历史带上去import requests import time BASE_URL https://api.example.com/v1/chat/completions API_KEY sk-你的key MODEL your-model-name def chat_once(messages, max_retries3): headers { Authorization: fBearer {API_KEY}, Content-Type: application/json } payload { model: MODEL, messages: messages, temperature: 0.7, max_tokens: 400 } for attempt in range(max_retries): try: resp requests.post(BASE_URL, jsonpayload, headersheaders, timeout30) if resp.status_code 200: return resp.json() elif resp.status_code 429: print(触发限流重试中……) time.sleep(2 ** attempt) continue else: resp.raise_for_status() except requests.exceptions.Timeout: if attempt max_retries - 1: raise time.sleep(2 * (attempt 1)) raise RuntimeError(API调用失败) # 初始消息列表先给模型设定身份 messages [ {role: system, content: 你是客服小A。回答亲切简洁每句不超过20个字。} ] print(客服小A已上线输入exit退出。) while True: user_input input(你) if user_input.lower() exit: break messages.append({role: user, content: user_input}) resp chat_once(messages) assistant_msg resp[choices][0][message][content] messages.append({role: assistant, content: assistant_msg}) print(小A, assistant_msg)看到关键点了吗每轮用户输入都append一个user消息模型回复后再append一个assistant消息。这样messages列表始终完整保存着这段对话的“历史”模型也就能准确理解上下文。如果你发现模型忘了前几轮聊了什么不用怀疑一定是你没有把历史消息传过去。另外上面这段代码里我加了重试逻辑尤其是对429限流做了退避重试。这个在真实应用里非常有必要因为模型API不是你本地函数网络抖动、限流、超时都是常态。3.3 让AI按规矩说话结构化输出的实战聊天文本人看着爽但程序处理起来很痛苦。比如你想让用户输入一句话然后程序自动判断情绪、提取关键词、生成建议如果模型给你回一大段散文你还得用正则硬抠抠得头破血流。正确姿势是在system提示词里规定输出格式让模型直接返回JSON。下面是我的一个个人习惯写法import json import requests SYSTEM_PROMPT 你是文本分析助手。用户会输入一段日常描述你需要从文本中提取结构化信息。只输出JSON不要输出任何额外内容。 JSON格式如下 { 情绪: 正面|中性|负面, 关键词: [列表最多3个], 摘要: 不超过20个字, 建议: 给用户的一条简短建议 } user_input 我今天心情很压抑工作好累感觉什么都做不好。 messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_input} ] resp chat_once(messages) content resp[choices][0][message][content] print(原始返回, content)这里有个几乎每次都会遇到的坑模型有时候会乖乖输出纯JSON有时候会在外面套一层Markdown代码块比如把内容用json和包起来。如果直接json.loads一定会报错。所以需要一个清洗函数把代码块剥掉再解析def extract_json(text): text text.strip() if text.startswith(): lines text.splitlines() # 去掉第一行 json保留剩余部分 lines lines[1:] if lines[0].startswith() else lines # 去掉最后一行 如果存在 if lines and lines[-1].strip() : lines lines[:-1] text \n.join(lines) # 只保留第一对花括号之间的内容防止前后有杂音 start text.find({) end text.rfind(}) if start ! -1 and end ! -1: text text[start:end 1] return text try: result json.loads(extract_json(content)) print(情绪, result[情绪]) print(关键词, result[关键词]) print(摘要, result[摘要]) print(建议, result[建议]) except json.JSONDecodeError: print(JSON解析失败请检查模型输出。原始内容, content)这个extract_json函数虽然简单但能解决我遇到过的绝大多数格式污染问题。学会把模型输出“洗干净”再解析是做AI应用开发的一个基础技能。3.4 完整的day02小项目情绪记录助手把上面三块串起来我day02的收尾项目是一个“情绪记录助手”。可以把它理解成一个带记忆、带结构化输出的迷你应用用户连续输入几句感受程序自动提取情绪和关键词最后把结果保存到文件里。import json def chat_once(messages, temperature0.3): # 这里复用上面写的API调用函数 pass # 主流程 records [] print(情绪记录助手已启动输入退出结束。) while True: user_input input(你) if user_input 退出: break messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_input} ] content chat_once(messages) try: result json.loads(extract_json(content)) records.append({user: user_input, result: result}) print(f情绪{result[情绪]} 摘要{result[摘要]} 建议{result[建议]}) except Exception: print(解析失败已忽略这条。原始返回, content) with open(records.json, w, encodingutf-8) as f: json.dump(records, f, ensure_asciiFalse, indent2) print(f共记录 {len(records)} 条已保存到 records.json)这个项目看起来不起眼但仔细想想你的程序已经完成了完整的数据闭环接收自然语言输入调用模型提取结构化数据解析JSON落盘存储。后面要做的知识库问答、智能客服、日志分类本质上都是这个模式的变体。4. 常见问题与排查技巧实录4.1 day02最容易踩的五个坑这两天实操下来我把新手最容易翻车的地方整理了一下每一个都是我亲眼见过或亲身踩过的。第一个坑是认证问题。最常见的是忘记在headers里加Authorization字段或者key复制错了。报错一般是401代表API不认识你。处理办法很简单先检查key有没有多空格、有没有复制完整再确认headers字段名是否正确。第二个坑是messages结构不对。比如把role拼成了“systemmsg”或者漏掉了“messages”这个外层键。很多服务商返回400错误提示Invalid messages format。建议拿到报错先打印完整响应体绝大多数情况下错误信息里都写得很清楚了。第三个坑是把temperature调太大。有的同学想让模型“更有创造力”直接拉到1.5结果做数据提取时模型开始自由发挥输出的字段都变形了。不同任务用不同参数不要一个值走天下。第四个坑是max_tokens给太小。做JSON输出时如果这个值只够模型生成一半返回的JSON就是残缺的解析必然失败。我之前还天真地以为模型会按照我给的例子里那么短的格式输出结果结构一复杂直接就超了。建议给足余量比如要求生成200字内容时把max_tokens设到400以上。第五个坑是忽略usage字段。有的API返回里会带usage里面记录了prompt_tokens、completion_tokens等。不看这个字段你永远不知道自己一次请求到底烧了多少钱。尤其调试阶段在循环里反复调用API一天下来账单可能比你想象的高很多。4.2 一张表把报错和排查说清楚实际操作里遇到报错不用慌绝大多数都能归到下面几类。我把常见报错、原因和排查方向整理成了速查表用时直接对照。报错或现象可能原因快速排查与解法401 UnauthorizedAPI key错误或请求头缺失检查key是否复制完整headers是否带了Authorization400 Bad Requestmessages结构错误、参数类型不对打印完整请求和响应检查role拼写和字段名429 Too Many Requests触发了限流或余额不足加退避重试降低请求频率检查账户额度context_length_exceeded输入历史输出超过上下文窗口截断历史消息压缩system提示词或者切换更大窗口的模型JSON解析失败模型输出了额外文本或max_tokens截断用extract_json清洗给足max_tokens关闭温度随机性响应超时网络问题或请求内容过长设置更长的timeout拆分较长的输入中文截断max_tokens设太小明白token和字数的区别按需放大max_tokens这张表值得保存下来后面调RAG、调Agent时90%的报错还是这些老面孔。4.3 调试技巧把一次请求完整解剖开遇到神秘问题时光靠猜是没用的得学会解剖请求。我的习惯是三步走。第一步打印完整的请求payload和响应body。只打印choices里的content会把很多关键信息藏起来。把resp.json()整个打出来看usage、看finish_reason、看报错message信息全了判断才准。第二步用最小复现去判断问题。如果某次调用输出不对先把system提示词缩到最短把用户输入换成一句“你好”看问题还在不在。如果还在说明是基础调用层的问题如果消失了再逐步加回system提示词二分定位。第三步把每次请求的token开销记下来。我调试的时候会在代码里加一行print(resp.json().get(usage))这样能看到输入消费了多少、输出消费了多少。配合日志一起看既能控制成本也能判断上下文是不是在悄悄膨胀。5. 从day02往后Agent到底是什么以及下一步怎么走5.1 Agent不是玄学它就是“模型 工具调用 循环”我在day02快结束时开始认真研究很多人挂在嘴边的AI Agent。之前总觉得是个特别玄的东西实际把API调通之后再看发现Agent并没有那么神秘。它的核心结构可以拆成三块模型做决策工具做执行代码做循环。模型本身不会调数据库、不会发请求、不会操作任何外部系统它只能输出文本。但如果你在请求里带上“工具描述”列表一般叫tools参数模型就可以在需要时说“我要调用get_weather这个工具参数是city北京”。这时你的代码拿到这个结构化指令自己去执行真实的天气查询再把查询结果作为一条新的消息返回给模型。模型看到结果后再组织成用户能听懂的话回答。这就是一次完整的Agent调用。所以说到底Function Calling本质也是一种结构化输出和我前面做情绪提取时的JSON输出没有本质区别。你要求模型输出“情绪、关键词、建议”是输出JSON你要求模型输出“工具名、参数”也是输出JSON只是后者多了一圈“执行”动作。想通这一点Agent的门槛就低很多了。5.2 下一步学习路线的建议如果day02的基础API调用已经跑通后面的路我建议按顺序走。先搞懂流式输出。你现在用的是一次性返回完整结果体验上会有延迟真实的聊天产品几乎都用流式输出SSE逐字返回。学这个不仅能改善体验还能理解为什么很多应用能做到“边想边说”。再学RAG。RAG可以理解为给模型外挂一个知识库把用户问题相关的资料先检索出来再塞进prompt让模型参考回答。这是解决“模型不会编外知识”和“私域知识问答”的主流方案也是目前企业落地最多的应用形态。接着再碰Agent框架。有了前面的基础不管是LangChain还是Coze你都会觉得它们只是把tools、messages、memory这些概念封装成了可视化节点底层原理你早就见过了。这时候学框架会非常快而且出了问题知道去哪查。最后学评估与测试。模型不是确定代码同样的输入可能每次输出不完全一样所以要用测试集、评价指标去盯质量。这是应用开发里容易被忽略、但生产环境必须的一环。day02结束之后的体会个人心得说一句day02结束的时候我把情绪记录助手跑通了按了几个回车看着它一条一条输出结构化JSON那一刻我意识到AI应用开发真正迷人的地方不在于模型本身多聪明而在于你开始把它当成一个可编程的服务用代码去调度、约束、纠错、沉淀数据。模型像一个能力极强但偶尔不靠谱的实习生你要做的就是给足指令规范、设计好流程兜底、把它的输出校验清楚。AI应用开发本质是设计一套“人、模型、数据”之间的协作协议这比单纯调prompt有意思得多。