尧图精选

10行代码跑通大模型API:从裸调用到Agent开发避坑指南

🕒 发布时间:2026/9/10 5:12:17 📁 来源:尧图网络
最近在折腾 Agent 开发我给自己定了个规矩不上框架、不碰现成项目就从最底层的大模型调用开始用 10 行代码把第一步跑通。这个决定让我少走了不少弯路但同样也踩了 4 个坑。如果你正在学 Agent、纠结要不要直接上 LangChain 这类框架这篇文章应该能帮你省下大半天时间。项目本身不复杂先建一个干净的 Python 环境用官方 SDK 调一次大模型 API让模型回一句话。区别在于我没有把这次调用当成“hello world”完事而是把它当成 Agent 的地基来打——每一次请求要传什么、返回什么、哪里容易出错、为什么出错我都做了记录。跑通之后我再用这 10 行代码延伸出工具调用、多轮记忆才真正理解了 Agent 的运作逻辑。这篇文章适合两类人一是完全没调过大模型 API、想知道第一步怎么迈的纯新手二是已经用框架写过 Agent、但没试过“裸调用”、想补底层认知的开发者。下面我按照实际操作顺序把代码拆解、完整流程、以及踩到的 4 个坑全部写出来。1. 项目定位与设计思路1.1 为什么要先跑通一次“裸调用”现在 Agent 开发的学习资源非常多随便一搜就是 LangChain、CrewAI、MetaGPT、AutoGen教程上来就教你建 Agent、挂工具、做多智能体协作。好处是上手快坏处是很多人写了一堆代码却连“模型到底是怎么被调起来的”都说不清楚。我的建议是先跑一次裸调用。所谓裸调用就是不走任何框架、不做任何封装直接拿代码请求大模型的 API拿到模型返回的文本。这一步能验证四件事开发环境能不能正常工作API Key 和网络通道有没有问题你选的模型名在平台上是否存在你发出去的 messages 格式是否正确。这四件事任何一个有问题后面所有 Agent 功能都没法跑。与其将来在框架里被各种抽象层挡住不如一开始就把变量控制到最少出问题也知道去哪查。在裸调用阶段我不需要考虑记忆、不考虑工具、不考虑 Agent 的“自主决策”只需要做一件事让模型收到你的话并且正确回复你。这个闭环一旦建立后面所有东西都是在它之上加逻辑。1.2 技术选型Python OpenAI 兼容接口技术选型我参考了当前行业的主流做法开发语言选 Python接口形式选 OpenAI 兼容格式。这里解释一下为什么。Python 在大模型生态里是事实标准不管哪个框架、哪个平台官方 SDK 和示例基本都是 Python 优先。而且 Python 写这类胶水代码非常快一个文件就能跑起来不需要处理编译问题。如果你只会 JavaScript/TypeScript也不是不行Node.js 同样有官方 SDK只是后续学习 Agent 框架时Python 的资料会多出很多。接口格式选 OpenAI 兼容是因为它已经成了行业通用协议。现在国内外的模型服务商绝大多数都提供了兼容 OpenAI 格式的接口你只需要改 base_url 和 model 名称代码结构完全不用变。这意味着你的学习成果可以平滑迁移到不同平台不会被某一家绑定。项目最终用的核心依赖只有一个openaiPython 包不需要装任何 Agent 框架。这一点很重要依赖越少出问题时的排查范围就越小。1.3 Agent 的最小闭环是什么很多人对“Agent”这个概念有误解以为 Agent 就是“会自己写代码的机器人”或者“能自动完成复杂任务的 AI”。如果从工程角度看Agent 的最小闭环其实是四个环节的循环感知接收用户输入或环境信息决策让大模型根据信息决定做什么执行调用工具、执行代码或搜索观察把执行结果反馈给模型进入下一轮决策。这四步循环第一步就是“让大模型能响应你”。所以裸调用不是 Agent 的全部但它是 Agent 的起点。我在设计这个项目时心里一直装着这个闭环才会有意识地往后延伸。2. 10 行核心代码逐行拆解2.1 代码全貌下面这段代码就是我说的“10 行代码跑通第一次大模型调用”。我删掉空行后数了一下正好 10 行。from openai import OpenAI client OpenAI( api_keysk-替换成你的Key, base_urlhttps://api.example.com/v1, ) resp client.chat.completions.create( modelyour-model-name, messages[{role: user, content: 你好请用一句话介绍你自己}], ) print(resp.choices[0].message.content)这段代码很短但它是一个完整的、可运行的请求闭环。不要觉得它简单就跳过后面所有 Agent 的功能都建立在它之上。下面我逐行拆。2.2 每一行在做什么第一行from openai import OpenAI是导入官方 SDK。安装命令很简单pip install openai如果你的 Python 环境有多个记得先激活虚拟环境再装。这个包把我们和 API 交互的细节全部封装好了不用自己拼 HTTP 请求。第 3 到 5 行是初始化一个客户端对象。api_key是身份凭证就像你进公司大厦的工牌base_url是 API 地址默认指向 OpenAI 官方但国内很多平台都有兼容接口你把地址换成平台的网关地址就行。第 7 到 10 行是真正的请求操作。client.chat.completions.create表示创建一个对话补全请求传了两个核心参数model你要用哪个模型messages对话消息列表。这里的messages是整个 Agent 开发的灵魂。它是个数组每个元素至少有role和content两个字段。role有三种system表示系统设定user表示用户输入assistant表示模型回复。你传入什么模型就在这个上下文中继续生成。最后一行print把模型回复打印出来。这里要注意访问路径resp.choices[0].message.content先取第一个回复选项再取里面的消息内容。2.3 参数选择temperature、max_tokens、stream刚跑通时很多人只传model和messages这没问题。但真要用于开发还有三个参数需要理解。参数作用我的推荐值说明temperature控制随机性0 到 20.7数值越高回答越发散越低越保守。Agent 做工具调用时可以调低到 0.2 左右减少中间步骤出错概率max_tokens限制最大生成 token 数视场景而定不设置可能消耗过多 token设置太小会截断回复stream是否流式输出False调试阶段设 False 拿到完整结果做对话应用再开启流式提升体验我调试时会把temperature设成 0因为我不想让模型“发挥”我需要稳定、可复现的结果。等到做聊天机器人才调回 0.7。参数没有绝对标准但你要知道每个参数影响什么否则后面出现问题根本无从排查。2.4 这段代码离 Agent 还差多远诚实说这段代码本身还不是 Agent它只是一个“对话模型调用”。它没有记忆、没有工具、没有自主决策。但它证明了最关键的事实模型可以被稳定调起来了。从这段代码到真正的 Agent中间还差三样东西记忆能力维护 messages 列表把历史对话带回去工具接口模型需要调用外部函数时怎么定义、怎么执行、怎么回填循环逻辑模型决策后系统执行工具再把结果送回模型直到任务完成。这三个能力我后面都会讲到。我想表达的是所有复杂系统都建立在这个 10 行代码的调用之上不要觉得它简单这是整个 Agent 的“原胞”。3. 完整实操过程从环境准备到首次对话3.1 环境准备我先说环境这是很多人忽略但最容易翻车的环节。我建议用 Python 3.10 以上版本。先建一个虚拟环境避免污染系统 Pythonpython -m venv .venv source .venv/bin/activate在虚拟环境里安装依赖pip install openai python-dotenv我同时装了一个python-dotenv它是用来加载环境变量的工具。API Key 这类信息尽量不要硬编码在代码里否则你把代码传到公开仓库时Key 就裸奔了。我的做法是项目根目录建一个.env文件OPENAI_API_KEYsk-你的Key OPENAI_BASE_URLhttps://api.example.com/v1 MODEL_NAMEyour-model-name然后在 Python 里用dotenv加载from dotenv import load_dotenv import os load_dotenv() api_key os.getenv(OPENAI_API_KEY) base_url os.getenv(OPENAI_BASE_URL) model_name os.getenv(MODEL_NAME)很多平台的 Key 是按用量计费的一旦泄露被拿去刷量损失就得自己承担。我习惯在代码里不写任何真实 Key而是全部从环境变量读取这是做项目第一课。3.2 写代码、跑起来准备好之后把核心代码放到一个main.py文件里。我第一次运行时在终端执行python main.py结果等了几秒钟终端打印出了模型回复。那一刻其实没有太多兴奋更多的是“哦通了”。但再回头想这个“通了”背后包含了很多环节DNS 解析、TLS 握手、鉴权、请求格式校验、模型推理、响应解析。任何一个环节有问题都不会有这个输出。如果你也想复现我建议把请求超时时间也加上避免网络卡住导致程序一直挂起resp client.chat.completions.create( modelmodel_name, messages[{role: user, content: 你好}], timeout30, )timeout参数在 SDK 里可以直接传如果 30 秒内没有响应程序会抛异常方便你及时发现问题。3.3 如何判断真的成功了很多人以为“打印出了内容”就是成功其实还不够。我建议你打印完整的响应对象仔细看一次print(resp)完整响应里包含几个关键字段id这次请求的唯一标识排查问题时可以通过它找日志choices模型生成的候选列表这里我们只取第一个usagetoken 使用量包括输入 token、输出 token、总 tokencreated请求创建时间戳。我会特别关注finish_reason它在choices[0]里。如果值是stop说明是正常结束如果是length说明 max_tokens 不够回复被截断了。平时开发时我会把响应摘要打到日志里方便追踪每次调用的 token 消耗。这在大模型应用里不是小事——成本分析全靠它。3.4 做到“可控”才算真正会调用跑通一次调用不算什么真正会调用是要让这次调用可控。我做的第一件事是加一个简单的错误处理框架try: resp client.chat.completions.create( modelmodel_name, messagesmessages, timeout30, ) return resp.choices[0].message.content except Exception as e: print(f请求失败: {e}) return None大模型 API 本质上是一个远程服务网络抖动、限流、服务端过载都是常态。写 Agent 代码时每次模型调用都应该有异常处理。不要觉得多此一举真正跑到生产环境你就知道了模型调用是最容易出问题的外部依赖之一。再往上一层我还会加“重试”。比如遇到 429限流或 5xx服务端错误等待几秒再重试一次import time for attempt in range(3): try: resp client.chat.completions.create(...) return resp.choices[0].message.content except Exception as e: if attempt 2: time.sleep(2 * (attempt 1)) else: raise这就是简单重试足够用。等以后你用到流式输出、函数调用时再考虑更完善的重试策略。4. 踩过的 4 个坑每一个都值得记下来4.1 坑一API Key 没生效报 401现象很典型代码完全按照示例写的但一运行终端报错401 Invalid authentication credentials我一开始以为是 SDK 版本问题后来发现根本不是。问题出在 API Key 的配置方式上。我当时的 Key 是直接复制到代码里的看起来没问题但仔细对比后发现复制时多了一个空格。这个坑的真正常见原因有三个Key 前后有隐藏空格或换行符平台开启了 IP 白名单你当前机器 IP 不在允许列表里用了已注销或过期的 Key。排查方式也简单先打印出配置的 Key 和长度确认没有多余字符再去平台后台确认白名单最后用一个最简单的请求测试 Key 是否有效。这里我有个心得一定要先用一个单独的、极简的 Python 文件验证 Key不要把它混在业务代码里。我后来每次对接新平台都是先写一个 5 行的测试脚本只做一次最小请求通了再往下写。4.2 坑二模型名写错或渠道不支持第二个坑是模型名。我的代码里写的是modelgpt-4结果报错404 The model does not exist真实原因是我使用的平台并不提供名为gpt-4的模型或者说它提供了兼容接口但模型名是另一种写法。比如有些平台把模型命名为gpt-4-32k有些则需要带版本后缀gpt-4-0125-preview。模型名少一个后缀、多一个短横线请求都会失败。这个坑的排查方法有三种查询平台文档中的模型列表调用接口主动拉取模型列表比如client.models.list()在平台控制台看模型名称说明。我自己写了一个小脚本用来列出账号能访问的全部模型models client.models.list() for m in models.data: print(m.id)这样可以快速确认到底有哪些模型可以用避免瞎猜。从那以后我每换一个平台第一步永远是拉一次模型列表看一眼再动手。模型名这件事还有个更深的影响它会直接影响 Agent 的能力边界。比如有些模型不支持函数调用你用这种模型做工具调用怎么调都会失败。所以在选模型时不仅要看能不能回话还要确认是否支持工具调用、上下文长度是否够用。4.3 坑三把记忆当成了多轮对话第三个坑是我跑通单次调用后想做多轮对话时踩的。我一开始的理解是我把第一轮的回复直接拼到messages里再发一次模型应该就能记得之前聊过什么。结果它确实“记得”了但一切建立在一种很朴素的方式上——把历史消息原样传回去。真正的坑在于很多人没有维护 messages 列表而是只存了一个字符串变量把以往的对话用换行拼接到content里再发给模型。这样的问题是系统指令system、用户消息、模型回复被揉在一起模型的上下文格式会混乱经常出现奇奇怪怪的回答。正确的做法是始终维护一个结构化的消息列表messages [ {role: system, content: 你是一个乐于助人的助理。}, ] while True: user_input input(我) messages.append({role: user, content: user_input}) resp client.chat.completions.create( modelmodel_name, messagesmessages, ) assistant_msg resp.choices[0].message.content messages.append({role: assistant, content: assistant_msg}) print(fAI{assistant_msg})每次请求把整个messages列表传过去它会作为模型的“记忆”存在。这个列表就是 Agent 记忆的最初级形态。但这里还有一个隐含的坑token 会越积越多。当对话达到几十轮后消息列表会变得很长成本上升、响应变慢、还可能超出模型的上下文窗口。所以后面必须引入截断策略比如只保留最近 10 轮对话或者把早期对话摘要后放进系统提示。Agent 的记忆远不止“多轮对话”但多轮对话是记忆的地基。你连结构化消息列表都没建好后面做 RAG、向量记忆都会很吃力。4.4 坑四工具调用结果回填格式不对第四个坑是我从“纯对话”迈向“Agent”时遇到的也是我觉得价值最大的一个。大模型本身不能执行真实操作它只能“建议”你应该调用某个工具。典型的调用过程是你定义一个函数比如获取天气的工具你把这个函数的描述、参数格式发给模型模型判断用户需要查天气时返回一个tool_calls结构里面包含函数名和参数你的程序执行真实函数拿到结果把结果以roletool的消息传回给模型模型基于结果生成最终回答。听起来顺理成章实际写的时候就容易踩坑。我第一次做的时候在第 5 步传回格式写错了。返回结果的 messages 没有与 assistant 之前的tool_call_id关联导致模型报错Invalid parameter: messages with role tool must be a response to a preceding message with tool_calls.这个报错信息翻译过来就是你传的 tool 结果没有对应上一次 assistant 消息里的 tool_calls 请求。正确的做法是当模型返回tool_calls时你把这一整条 assistant 消息原样加入messages然后每个工具执行结果都用roletool单独一条消息加入且每条都要带上对应的tool_call_id。官方的标准序列大概是# 加入 assistant 的 tool_calls 消息 messages.append(resp.choices[0].message) # 加入工具执行结果 for tool_call in resp.choices[0].message.tool_calls: result run_tool(tool_call.function.name, tool_call.function.arguments) messages.append({ role: tool, tool_call_id: tool_call.id, content: str(result), })然后再次调用模型模型会结合工具结果给出最终回复。这个坑之所以重要是因为它就是 Agent 和普通聊天机器人的分水岭。Agent 的“行动能力”完全依赖这条调用链模型给指令程序执行回填结果模型再决策。链条任何一个环节的格式不对整个循环就断了。我当时调试了很久后来是把模型返回的完整消息体打印出来一行一行对比官方规范才找到问题。所以遇到工具调用相关报错先打印原始响应别靠猜。5. 从第一次调用到真正的 Agent5.1 Agent 的最小闭环跑通前面的流程后我对 Agent 的认识终于从概念变成了具体代码。这里以“查天气”为例演示一下怎么从 10 行代码出发拼一个最小 Agent。第一步定义一个工具函数。因为手边没有真实天气 API我用一个假函数模拟def get_weather(city: str): # 真实项目里这里会请求天气服务 weather_map { 北京: 晴25度, 上海: 小雨22度, 广州: 多云28度, } return weather_map.get(city, 暂无数据)第二步告诉模型有这个工具。这是在messages之外传一个tools参数用 JSON Schema 描述函数tools [ { type: function, function: { name: get_weather, description: 查询指定城市的天气, parameters: { type: object, properties: { city: {type: string, description: 城市名} }, required: [city], }, }, } ]然后跑循环。用户说“北京天气怎么样”模型判断要查天气返回tool_calls程序执行函数拿到结果后回填再让模型组织语言回复。这个循环跑起来你手上就是一个“会调用工具”的最小 Agent 了。我没有在这里放出完整循环代码因为完整循环需要处理流式、并发等细节。但核心思路你已经清楚了工具的注册、执行结果的回填就是前面 4.4 讲的坑。把那个坑填平Agent 的行动能力就打通了。5.2 框架用不用什么时候用我觉得这是 Agent 开发里最值得说的问题。我的建议是先裸写再用框架。裸写的价值在于你知道每一次请求背后发生了什么。等你理解了工具调用、记忆、循环这些概念再上手 LangChain、CrewAI 这类框架就不会有“黑盒恐惧”。框架能帮你省掉大量重复代码比如多 Agent 编排、Prompt 模板、任务分解。但如果你没有底层认知框架封装得越漂亮你越难排查问题。我个人的时间线是先花两天时间裸写从调用到工具、到多轮对话然后开始用框架重写同一个示例对比自己的代码和框架的差异最后再决定哪些场景直接用框架、哪些场景保持裸写。很多 Agent 项目的失败不是框架不好而是使用者根本不理解模型调用机制遇到一个报错就卡死。所以动手写框架前先把裸调用这步练扎实。5.3 接下来可以怎么学如果你跟着这篇文章跑通了第一次调用也理解了那 4 个坑下一步的学习路线我建议这样走第一步把单次调用封装成函数支持多轮对话第二步实现一个简单的工具调用循环让模型能调用你写的函数第三步给 Agent 增加长期记忆比如用向量数据库保存历史信息第四步拆分多个 Agent让它们通过消息协作完成任务第五步这时候再回头看框架你会理解和吸收得很快。我在实际做的时候一直保持着一个习惯每引入一个新概念就回到那 10 行代码问自己一句这个问题是不是改一下 messages 或参数就能解决。这个习惯让我的学习路径变得特别清晰。拿工具调用来说它并不是一套全新的魔法本质上只是让模型输出的结构化格式由普通文本变成了tool_callsJSON。理解了这一点Agent 的“自主决策”就没什么神秘的了——无非是循环里根据模型输出类型做不同的分支处理。聊到这儿这次“从零手撸 Agent”的核心内容就全部讲完了。如果你也想动手做我建议不要纠结框架和工具链先打开编辑器把第 2 节那段代码跑通。只有跑通一次你才算真正跨进了 Agent 开发的门。后面那 4 个坑你大概率也会遇到但我希望你看完这篇文章后能少花一点时间在排错上把这些时间留给真正的设计思考。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →