很多想转 Agent 开发的朋友第一次动手时都会卡在同一个地方明明大模型 API 文档写得挺清楚可真要自己把一句话变成一次真实调用却不知道该从哪行代码写起。还有人误以为“会调 API”就是“会写 HTTP 请求”结果 curl 能通换到代码里就各种报错。这个项目标题其实点破了一条非常关键的路径用 SDK 方式完成 LLM 基础对话看似只是入门但它同时是 Agent 学习的地基也是不少面试官会拿来考察候选人的经典题目。下面我把整个思路、代码、参数、常见坑一次讲透确保你能直接照着复现并且弄明白每一步背后的原因。1. 为什么把 SDK 调 API 当作 Agent 学习的第一课1.1 从“会问问题”到“会构建对话系统”很多人第一次接触大模型是在聊天网页里输入一段话拿到一段回答。这个体验很神奇但它和“用代码构建对话能力”之间的距离比想象中大得多。因为网页里的对话是产品团队已经封装好的而你真正要掌握的是这层封装底下的核心逻辑把用户输入的消息按照特定格式组织起来发送给模型服务再把模型返回的结果解析出来变成你自己的产品功能。用 SDK 完成基础对话就是这个从“使用者”到“构建者”的最小跨越。它不需要你懂复杂的分布式系统也不需要你背下全部 API 文档只需要理解一套消息结构、一个调用方法、一段返回解析代码就能跑通第一版程序。这个程序虽然简单但五脏俱全有输入、有输出、有网络交互、有异常处理已经具备了一个“对话系统”的雏形。我在帮朋友改简历时发现不少候选人在项目经历里写“熟悉大模型 API 调用”但一问细节就露馅messages 里为什么要有多个角色assistant 消息为什么要回传流式返回该怎么处理这些恰恰是 Agent 开发里最常用的知识点。所以基础对话不是“太简单不值得写”而是“看着简单但能讲深的人不多”。1.2 为什么我推荐 SDK而不是直接拼 HTTP 请求刚入门的人容易产生一个误区觉得 SDK 是“别人封装好的东西”自己“手写 HTTP 请求”才显得厉害。这个想法我劝你尽早放下。直接拼 HTTP 请求你需要自己处理的事情包括拼接 URL、构造鉴权 Header、处理请求体序列化、解析 JSON 响应、处理各种非 200 状态码、手动实现流式读取。这些工作不是不能做但做完之后你得到的是“重复造轮子”而不是“更深入的理解”。SDK 的价值在于它是官方或社区维护的客户端库已经把鉴权、重试、超时、流式解析这些“脏活”都封装好了。你只需要按它暴露的接口传入参数就能拿到结构化的返回对象。这就像你开车不需要自己造发动机但你需要知道油门、刹车、方向盘分别是干什么用的。SDK 就是那辆已经组装好的车你要学的是“怎么安全高效地驾驶它”而不是从炼钢开始。更实际的一点是大多数 LLM 服务商都提供 OpenAI 兼容的接口。这意味着你只要学会用 OpenAI 的 SDK 结构切换到 DeepSeek、智谱、通义等平台时几乎只需要改 base_url 和 api_key。这种“一次学习、多处复用”的优势在 Agent 项目里尤其重要。因为 Agent 框架底层要对接模型一个稳定的调用层可以让你把精力放在逻辑编排上而不是整天和 API 返回值较劲。1.3 这条路线对 Agent 学习和面试的实际价值先说 Agent 学习。Agent 的本质是让模型在循环里自主决策观察输入、决定行动、调用工具、拿到结果、再继续下一步。这个循环的第一环就是“把文本传给模型并拿到回应”。如果你的基础对话调用都写得磕磕绊绊后面接工具、接记忆、接规划都会连锁出问题。反过来当你把一次调用的参数、消息结构、异常处理吃透再去理解 ReAct 循环、Function Calling、Agent 框架就会顺畅很多因为你已经知道模型在“背后”到底是怎么被调起来的。再说面试。面试官问 API 调用相关问题时很少只问“你会不会写”更多是问你“知不知道为什么”。比如为什么要用环境变量存 Key多轮对话为什么要传历史消息如果返回超时怎么办流式和非流式有什么区别这些都是基础对话的延伸问题也是实际项目里真正会遇到的工程问题。把基础对话做扎实等于你手里有一套能应对追问的素材库而不是只背了几个名词。2. 动手前必须搞清楚的核心概念2.1 消息结构messages 才是对话的主心骨第一次看大模型 API 文档的人最常见的困惑是为什么请求体里只有一个 messages 数组没有单独的“问题”字段这是因为现代的 Chat 类模型把对话看作“一系列消息的序列”而不是“一问一答”。模型要理解当前这次提问的上下文必须知道前面发生了什么所以你要把历史消息一并传过去。每个消息对象通常包含两个字段role角色和 content内容。最基本的结构是这样[ {role: user, content: 你好请介绍一下你自己} ]一次只传一条 user 消息模型也能回答这就是“单轮对话”。但如果用户紧接着追问“那你能做什么”你不把第一轮的问答回传模型就会失去上下文凭空生成一个不相关的回答。所以多轮对话的请求体通常是多段消息的累积[ {role: user, content: 你好请介绍一下你自己}, {role: assistant, content: 你好我是一个AI助手可以帮你解答问题。}, {role: user, content: 那你能做什么} ]这里有个容易踩的坑如果你把 user 消息连续传两条中间没有 assistant 回复部分模型的接口会报错或表现异常。原因很简单——真实对话里不可能出现“用户说完用户又说”的情况模型默认你的消息序列是符合对话逻辑的。所以构造消息列表时要保证 user 和 assistant 交替出现以 user 提问开始也尽量以 assistant 回复作为历史段落的结束。2.2 角色体系system、user、assistant、tool 各管什么角色体系是理解对话行为的关键。最常见的三个角色是 system、user、assistant在 Agent 场景下还有 tool。它们的分工可以这么理解system 消息是“给模型定的规矩或人设”比如“你是一个乐于助人的助手”“请用简洁的语言回答”。它不参与常规对话但对模型的回答风格和边界有很强的引导作用。user 消息代表“用户输入”也就是需要模型响应的内容。assistant 消息代表“模型的回应”。在单轮调用里你不会主动写它但在多轮和 Agent 循环里你需要在把模型上一次的回复追加进消息列表时标记成 assistant。tool 消息通常配合 Function Calling 使用当模型决定调用某个工具并且工具执行完毕之后工具的结果会以 tool 角色回传给模型模型再基于这个结果继续回答。这里有一个常见误区有人以为 system 消息越详细越好于是写一大段“人设”。实际上system 消息也会有长度限制而且过长的 system 会挤占本来可以给真实对话使用的上下文窗口。更合理的做法是把稳定不变的规则放在 system 里把每次变化的输入放在 user 里。在 Agent 设计里system 消息也经常被用来注入当前时间、用户的元信息、工具说明等“始终需要模型知道”的内容这是一种非常实用的技巧。2.3 API Key 安全最容易忽略但最致命的一条既然标题里提到了“适配面试”那 API Key 安全问题几乎必被问到。很多初学者图方便直接把 Key 写成字符串常量塞进代码里api_key sk-xxxxxx这段代码如果只是本地学习问题不大。可一旦你把它推到 GitHub、发给同事、放进公开的在线编辑器就等于把账号权限送给了别人。别人的调用费用记在你头上还可能因为异常调用触发限流甚至封号这就是典型的“用到的时候才后悔”的事故。安全的做法是把 Key 放到环境变量里代码从环境变量读取import os api_key os.getenv(DEEPSEEK_API_KEY)在本地开发时你可以用一个 .env 文件管理环境变量但一定要把 .env 加进 .gitignore避免误提交。在服务器部署时则通过容器或 CI 平台的密钥配置功能注入环境变量。还有一条容易被忽略的原则前端代码里永远不要直接放 Key。因为浏览器里的任何字符串用户都能通过开发者工具看到。正确做法是把调用藏在后端由后端转发请求前端拿到的只是结果。日志也是泄露重灾区。很多人会在调试时把完整请求和响应打出来结果 API Key 或者对话内容全进了日志。真要打日志时记得对敏感字段做脱敏Key 只显示前几位和后几位中间打星号。这条经验来自实际教训——我在排查线上问题时真的见过因为把请求体原样打到日志里导致用户敏感信息外泄的情况。2.4 模型与 OpenAI 兼容协议怎么选国内可用的模型服务很多DeepSeek、智谱、通义千问等都有自己的 URL 和 Key 体系。但它们大多提供 OpenAI 兼容的接口所以你完全可以用 OpenAI SDK 的客户端只改两个参数就能完成接入from openai import OpenAI client OpenAI( api_key你的Key, base_urlhttps://api.deepseek.com )这种设计给学习带来的好处是你只学一套 SDK 的用法就能适配多家厂商。这在项目里也意味着更低的切换成本——如果某家服务不稳定或价格变了你可以迅速换到另一家业务代码几乎不动。模型名这块也要注意。每家服务商的模型名不一样比如 DeepSeek 的deepseek-chat、deepseek-reasoner你需要根据自己的需求选择。很多人遇到的 400 报错就是因为模型名填错了。在 Agent 场景里选模型还要额外考虑三点上下文窗口多大、是否支持 Function Calling、单位 token 的成本是多少。这些信息在各家官方文档里都有明确说明动手之前养成先查文档的习惯能省下大量试错时间。3. 手把手实操用 Python SDK 完成基础对话3.1 环境准备与依赖安装实操前先把环境准备好。我用的环境是 Python 3.9安装了 openai SDK。如果你电脑上还没有直接装pip install openai python-dotenvopenai 这个包会同时提供 ChatCompletion 相关的调用能力python-dotenv 用来读取 .env 文件。装完之后就可以开始写第一段代码了。如果你是第一次安装可能遇到两个小问题一是网络原因导致下载慢这个可以用国内镜像源加速二是旧版本缓存冲突建议装之前先确认版本别让系统里残留的旧版 openai 包干扰你。检查方式很简单pip show openai | grep Version如果版本号是 0.x建议先升级到 1.x 再继续因为新版 SDK 的接口风格和老版本差别很大网上的教程多数也是基于新版写的你跟着看会减少很多“代码明明一样却报错”的情况。3.2 第一段可运行的对话代码直接上代码。我在本地用一个最简单的脚本验证调用效果如下import os from openai import OpenAI client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlos.getenv(DEEPSEEK_BASE_URL, https://api.deepseek.com) ) def chat_once(user_input: str) - str: resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: 你是一个乐于助人的AI助手。}, {role: user, content: user_input} ], temperature0.7, max_tokens512 ) return resp.choices[0].message.content if __name__ __main__: print(chat_once(你好请用一句话介绍你自己))这段代码不足 20 行但它已经是完整的“基础对话”实现了。跑的流程很简单构造一个 OpenAI 客户端传入一个消息列表调用 chat.completions.create然后从响应对象里取出模型回复并返回。注意几个细节。第一client.chat.completions.create是新版 SDK 的调用方式旧版本是openai.ChatCompletion.create两者完全不一样。你看到旧写法的时候不要直接复制到新环境里因为那是 0.x 版本时代的 API。第二返回结果 resp 是一个对象不是纯字符串。要拿到文本得从resp.choices[0].message.content里取。这个结构不是设计得复杂它背后承载的是模型可能返回多个候选、以及完整返回元信息的能力。第三我在 system 里加了一句人设这会让回复更规范也能让你直观感受到 system 对回答风格的影响建议你删掉试试对比一下。3.3 关键参数逐个拆解很多人写完第一段能跑的代码就停了觉得“能跑就行”。但面试或者实际调优时你总会被问到“这个参数是什么意思、该调成多少”。所以我把几个高频参数单独拎出来说。temperature控制随机性。取值 0~2值越低回答越确定、越保守值越高越有创造性。基础问答、代码生成类任务建议调低比如 0.2 或 0.3创意写作、头脑风暴可以调高到 0.8~1.0。我自己的经验是Agent 场景里别用太高否则模型容易“灵机一动”偏离规划路径。max_tokens限制最大生成 token 数。这里的“token”不是汉字数1 个汉字大概对应 1~2 个 token。如果你不设模型可能一直写到上下文上限如果设太短回答会被截断。在 Agent 场景里工具调用的结果往往不会太长你可以根据实际需要设置精打细算也能控制成本。top_p核采样和 temperature 类似都是控制随机性的参数。两个都调容易互相干扰官方建议一般只调其中一个。我通常固定 top_p 为 1只靠 temperature 调风格。习惯“解释清楚每个参数”的人面试碰到这道题会特别加分。stream是否流式返回。默认是 False也就是等模型把整个答案生成完再一次性返回等待时间长但代码简单。设为 True 之后模型会边生成边返回体验上更像“打字机”效果Agent 场景里很多“实时反馈”都靠它实现。建议你写一个小测试把这些参数分别改一改观察输出有什么变化。这种“亲手试”的记忆比背文档牢固得多也能帮你日后回答“如何调优”这类问题时更有底气。3.4 让对话“聊起来”多轮消息累积单次调用能跑通之后下一步就是做一个真正能连续聊天的循环。我写代码时最常用的多轮对话结构长这样import os from openai import OpenAI client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlos.getenv(DEEPSEEK_BASE_URL, https://api.deepseek.com) ) messages [{role: system, content: 你是一个知识渊博的助手回答尽量简洁。}] while True: user_input input(你) if user_input.lower() in {exit, quit, q}: break messages.append({role: user, content: user_input}) resp client.chat.completions.create( modeldeepseek-chat, messagesmessages, temperature0.7 ) assistant_reply resp.choices[0].message.content print(AI, assistant_reply) messages.append({role: assistant, content: assistant_reply})这段代码的核心思路就两个字累积。每次把用户新输入追加到 messages调用模型后再把模型回答也追加进去。这就保证了下一轮对话时模型能看到前面全部内容形成连续记忆。但这个“记忆”不是无限的。随着对话变长messages 越来越长请求里的 token 会迅速膨胀最终触达上下文窗口上限导致 400 或者 cost 过高。实际项目里你需要一个简单的截断策略比如只保留最近 10 轮消息或者当总 token 接近上限时把最旧的消息丢掉。这个策略在 Agent 里有一个专业叫法上下文管理。等你看完下面章节就会发现基础对话里的每条经验后面都会长大成一个更复杂的问题。4. 从基础对话到 Agent你还需要迈过哪几步4.1 为什么 Agent 不能只靠单轮对话单轮对话解决的是“你问一句、模型答一句”的场景。但 Agent 不一样它要完成的目标往往是多步骤的。举一个最简单的例子你想让 Agent 帮你查一下“今天北京的天气并且提醒我适不适合跑步”。如果模型只靠记忆和推理回答大概率是“我无法实时获取天气数据建议你打开天气应用”。但接入工具的 Agent 会这样运作先判断“查天气”需要调用一个天气查询工具然后生成一个工具调用参数你执行工具返回数据再把数据交给模型模型基于这个结果给出最终建议。整个过程里模型要被调用至少两次而且第二次调用时它已经需要知道第一次工具调用的结果了。所以多轮消息结构不是可选项而是 Agent 循环的底座。这就是为什么我在标题里强调“适配 Agent 学习”——基础对话不是终点它是理解 Agent 循环的最小细胞。你只有先掌握消息累积、角色区分、返回解析才能在后面理解 ReAct 循环Thought思考、Action行动、Observation观察三个阶段在代码层面如何对应到 messages 的增删和模型的反复调用。4.2 上下文管理别让记忆拖垮模型前面提到无限累积 messages 会导致超长请求这在 Agent 场景里更严重。因为 Agent 每执行一步动作都会把“模型思考、工具名称、工具参数、工具结果、模型下一步思考”连续写入消息序列。十几轮工具调用下来消息可能轻松达到几万字。我在 Agent 项目里常用的一个简单策略是“滑动窗口”只保留最近 N 轮消息更早的内容直接丢弃。这种做法会损失部分早期信息但对大多数任务来说是性价比最高的方案。稍微进阶一点的做法是“关键信息摘要”定期把旧消息交给模型生成一个总结然后把总结作为 system 或上下文的一部分保留下来。这个思路你在很多 Agent 框架源码里都能看到。还有一个实用细节在把历史消息传给模型时适度精简 token 能明显省钱和提速。比如日志里的一长串调试输出在 Agent 调用工具时往往价值不大你可以截断它只保留前 500 个字符。这块属于工程上的取舍需要你在“信息完整度”和“成本响应速度”之间做平衡。我的建议是先按默认直传跑通功能再根据实际效果逐步加截断策略。4.3 流式输出让对话“边说边出”如果你做过面向用户的对话应用就一定会遇到流式需求。非流式模式下用户需要等模型把整段话生成完慢任务可能要等几十秒体验非常糟糕。流式模式下模型每生成一小段就通过 SDK 回调返回一次前端可以实时更新用户觉得“它正在思考”体验改善非常明显。用 SDK 开启流式很简单代码就多一个参数和一次循环stream client.chat.completions.create( modeldeepseek-chat, messagesmessages, streamTrue ) for chunk in stream: if chunk.choices and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end)每一个 chunk 里choices[0].delta.content是这一小段新增的文本。把这些小段拼接起来就得到了完整的回答。注意流式模式下第一个 chunk 通常没有 content只有 role所以代码里要做空判断。还有一个细节是流式返回的最终使用和普通返回一样需要你自己拼装完整文本并且把它作为 assistant 消息追加到 messages 里——这个动作一定不能漏否则下一次对话模型就丢了上下文。4.4 Tool CallingAgent 的“手”是怎么长出来的这部分其实是 Agent 学习里最值得展开的一块但基础对话阶段你只需要理解个大概。Tool Calling也叫函数调用、Function Calling的功能是在请求里声明“可用的工具列表”模型看到用户输入后如果判断需要调用某个工具就会在返回内容里结构化地输出“工具名参数”而不是直接给出最终答案。SDK 代码层面的做法是这样的tools [ { type: function, function: { name: get_weather, description: 查询指定城市的实时天气, parameters: { type: object, properties: { city: {type: string, description: 城市名如北京} }, required: [city] } } } ] resp client.chat.completions.create( modeldeepseek-chat, messagesmessages, toolstools )当返回里有tool_calls字段时你就知道模型决定调用工具了。接下来你的程序负责真正执行这个函数、拿到结果然后把结果放进一条 tool 消息里回传给模型。这一步之后模型才会结合工具结果给出最终回答。整套流程看着有点绕但它就是 Agent 执行任务的核心机制之一建议你把基础对话跑顺之后再拿这个案例一步步打断点调试会学到大量细节。5. 面试高频问题与问题排查实录5.1 高频问题速查面试官到底在问什么基础对话听起来简单但面试官能在这一块问出很多层次。我整理了一张问题表数字越高、难度越大你可以自测一下自己能答到第几层层级问题考察点1你有没有用 SDK 调过 LLM是否真写过代码2消息列表为什么要传多轮历史是否理解上下文机制3system/user/assistant 的区别是什么是否理解角色体系4API Key 怎么保护工程安全意识5temperature 和 max_tokens 分别影响什么参数理解深度6流式返回如何处理是否处理过真实场景7如果请求超时你会怎么处理容错设计能力8多次调用模型如何管理上下文避免超出窗口架构思维9Tool Calling 和普通调用的区别Agent 基础认知10让 Agent 调用工具时怎么避免它“瞎调用”安全性、可靠性思维这张表里的第 7 题是实际项目里大家抱怨最多的一类问题。比如请求超时了新手往往只会“再试一次”但资深一点的做法是设置合理的超时时间、加入指数退避重试、针对幂等操作做重复请求保护、对不可恢复错误快速失败并返回友好提示。这些在执行层面其实就几行代码的事但体现的工程能力是质的差别。5.2 常见报错与排查办法我把自己遇到过的报错整理成一个速查表你照着排查能省不少时间报错信息常见原因处理办法400 Bad Request提示 model 名不支持模型名填错或该模型在目标服务上不可用去官方文档查最新模型名确认平台是否支持该模型401 Authentication Failsapi_key 错误、环境变量没加载成功打印 os.getenv 结果确认非空检查 Key 前后是否有空格429 请求过多或超过配额账户余额不足、触发限流检查账户余额按退避策略重试降频调用timeout / Read timed out网络不稳定、单次请求内容过长调大超时时间减少请求 token换更稳定的网络环境context length exceeded消息列表总 token 超出上下文窗口裁剪历史消息减少 max_tokens用滑动窗口JSON 解析报错模型返回了非法 JSON常见于让模型强输出 JSON 的场景在 prompt 里给示例用结构化输出/工具加一层容错解析我印象最深的一个场景是同事调 DeepSeek API一直报 401检查了半天才发现 .env 文件里 Key 的值是复制过来的后面带了一个换行符和空格。这类问题非常隐蔽尤其新手不太会想到。所以排查第一步永远是“打印出你到底传了什么”而不是盯着文档猜。另一个容易忽略的点是有些厂商的接口在不同区域/不同模型的 base_url 并不一样千万不要假设所有服务商都是同一个地址。5.3 项目里值得沉淀的工程习惯学完基础对话之后我给自己的项目沉淀了一套习惯也建议你照做。第一封装一个自定义的调用函数或类把 client 初始化、默认参数、异常处理、重试逻辑都收进去而不是在业务代码里到处裸调 API。这样接口升级时你只需改一个地方。第二所有对外暴露的调用都做日志记录但日志里绝不能包含完整 Key 和用户敏感信息只记录调用时长、模型名、token 使用量就够。第三把模型名、base_url、max_tokens 这些易变配置抽成配置项别写死在代码里方便切换不同模型或厂商。我见过很多“跑得通但不持久”的项目问题出在代码里到处都是硬编码。今天能用 deepseek-chat明天换了一个模型满屏都要改。从这个角度看基础对话入门时多花一点时间把代码结构理干净后面做 Agent 会特别受用。6. 给正在准备 Agent 方向的朋友几句实在话6.1 我的个人体会我自己刚开始学 Agent 时同样走过一段“看文档觉得懂了、写代码却卡住”的日子。后来发现问题不在理解能力而在我没有花时间把基础调用这条链路真正跑顺。现在带人做项目我坚持让每个人先不依赖任何 Agent 框架自己用 SDK 写一遍基础对话、多轮对话、流式输出、Tool Calling 四个环节。四个做完底层逻辑就通了再去看主流 Agent 框架源码脑子里会自动浮现“这不就是在帮我们做这些事吗”学习效率完全不一样。6.2 最后分享一个小技巧写基础对话代码时建议你开一个临时调试脚本把每次调用的“消息列表、返回状态、token 用量、耗时”全部打印出来。这个习惯能帮你快速定位很多问题。比如某次回答变得很怪你一眼就能看到是不是历史消息里混入了奇怪的内容某次调用很慢你能迅速判断是模型本身生成太长还是网络延迟。这些看起来零碎的数据恰恰是日后你从开发走向优化时最重要的实证基础。等你能轻松完成一个带多轮记忆和流式输出的对话程序再去尝试加一个简单的工具调用你会发现 Agent 的大门已经向你敞开了。
