很多想转 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_url="https://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_key=os.getenv("DEEPSEEK_API_KEY"), base_url=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com") ) def chat_once(user_input: str) -> str: resp = client.chat.completions.create( model="deepseek-chat", messages=[ {"role": "system", "content": "你是一个乐于助人的AI助手。"}, {"role": "user", "content": user_input} ], temperature=0.7, max_tokens=512 ) 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_key=os.getenv("DEEPSEEK_API_KEY"), base_url=os.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( model="deepseek-chat", messages=messages, temperature=0.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( model="deepseek-chat", messages=messages, stream=True ) 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 Calling:Agent 的“手”是怎么长出来的
这部分其实是 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( model="deepseek-chat", messages=messages, tools=tools )当返回里有tool_calls字段时,你就知道模型决定调用工具了。接下来你的程序负责真正执行这个函数、拿到结果,然后把结果放进一条 tool 消息里回传给模型。这一步之后,模型才会结合工具结果给出最终回答。整套流程看着有点绕,但它就是 Agent 执行任务的核心机制之一,建议你把基础对话跑顺之后,再拿这个案例一步步打断点调试,会学到大量细节。
5. 面试高频问题与问题排查实录
5.1 高频问题速查:面试官到底在问什么
基础对话听起来简单,但面试官能在这一块问出很多层次。我整理了一张问题表,数字越高、难度越大,你可以自测一下自己能答到第几层:
| 层级 | 问题 | 考察点 |
|---|---|---|
| 1 | 你有没有用 SDK 调过 LLM? | 是否真写过代码 |
| 2 | 消息列表为什么要传多轮历史? | 是否理解上下文机制 |
| 3 | system/user/assistant 的区别是什么? | 是否理解角色体系 |
| 4 | API Key 怎么保护? | 工程安全意识 |
| 5 | temperature 和 max_tokens 分别影响什么? | 参数理解深度 |
| 6 | 流式返回如何处理? | 是否处理过真实场景 |
| 7 | 如果请求超时,你会怎么处理? | 容错设计能力 |
| 8 | 多次调用模型,如何管理上下文避免超出窗口? | 架构思维 |
| 9 | Tool Calling 和普通调用的区别? | Agent 基础认知 |
| 10 | 让 Agent 调用工具时,怎么避免它“瞎调用”? | 安全性、可靠性思维 |
这张表里的第 7 题,是实际项目里大家抱怨最多的一类问题。比如请求超时了,新手往往只会“再试一次”,但资深一点的做法是:设置合理的超时时间、加入指数退避重试、针对幂等操作做重复请求保护、对不可恢复错误快速失败并返回友好提示。这些在执行层面其实就几行代码的事,但体现的工程能力是质的差别。
5.2 常见报错与排查办法
我把自己遇到过的报错整理成一个速查表,你照着排查能省不少时间:
| 报错信息 | 常见原因 | 处理办法 |
|---|---|---|
| 400 Bad Request,提示 model 名不支持 | 模型名填错,或该模型在目标服务上不可用 | 去官方文档查最新模型名,确认平台是否支持该模型 |
| 401 Authentication Fails | api_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 的大门已经向你敞开了。