想象一下这个场景:你决定进入 AI Agent 开发这个岗位,兴冲冲地打开第一个教程,结果满屏都是英文单词、括号、冒号和看不懂的符号,像在看天书。三分钟后,你默默关掉了页面,心里冒出一个念头:"我真的学得会吗?"
如果你有过这种感觉,别慌。今天这篇文章,就是写给此刻的你——零基础、没写过一行代码,但想往 AI Agent 开发走的人。
先给你吃颗定心丸:Python 是公认最适合新手的第一门语言,没有之一。它的语法接近自然语言,读起来像在读英文句子。你不需要先啃三个月底层原理再碰 AI,Python 可以直接上手。
更重要的是:Python 是 AI Agent 开发的"母语"。你要用到的 LangGraph、OpenAI 官方 SDK、各大模型 API,全都是 Python 生态。换句话说,你绕不开它,但也不用怕——它恰恰是最容易跨过的那道门槛。
这篇的目标很明确:学完之后,你能读懂并写出 Agent 开发中最常见的 Python 代码。我们只讲高频语法,不讲冷门技巧;每讲一个知识点,都会告诉你"它在 Agent 里到底怎么用"。
看完这一篇,你将拥有:搭好环境、写出第一个程序、玩转变量与容器类型、让代码学会判断和循环、用函数和类组织代码、让程序出错不崩溃、让多任务并发执行、解析大模型的 JSON 返回——最后还能亲手跑通一个"简易智能客服"。
阅读建议:准备好电脑,跟着每节的示例动手敲一遍。只看不练,等于白学。
写代码就像做饭,得先把锅灶备好。你的"厨房"只需要两样东西:Python 解释器和编辑器。
Python 解释器 负责"听懂"你写的代码并执行它。先说 Windows:去 python.org 下载 Python 3.10 或更高版本,双击安装包后,注意第一屏最下方有一个复选框"Add Python to PATH",一定要勾上。PATH 相当于系统用来"找命令"的地址簿,不勾的话,之后在终端里敲 python 会提示"不是内部或外部命令"。勾选后一路点"Install Now"即可。
再说 Mac:Mac 不预装完整的 Python,首次在终端使用 python3 时系统会提示安装 Command Line Tools,按提示装完即可;更推荐的做法是直接去 python.org 下载官方安装包,一路下一步。装完后有个小区别:终端里要用 python3 而不是 python 启动命令,这是 Mac 的惯例,记住就行,不用纠结。
编辑器(VS Code) 是你写代码的地方。去 code.visualstudio.com 下载免费版本,安装后打开左侧的扩展商店,搜索"Python",安装微软官方插件。装完打开任意一个 .py 文件,VS Code 右下角会提示选择解释器,选你刚装的那个版本。有了插件,写代码时就有语法高亮、自动补全和错误提示,体验完全不同,强烈建议配好。
装完后打开终端(Mac 叫终端,Windows 叫 PowerShell),输入下面这行验证:
python --version看到类似 Python 3.12.x 的输出,说明环境就绪。如果提示命令找不到,八成是 PATH 没配好:Windows 可以重新运行安装包勾选 PATH,或者手动把 Python 安装目录加进系统环境变量;Mac 直接用 python3 试试。
新手常见坑:电脑里可能同时存在 Python 2 和 Python 3,或者你装过 Anaconda,多版本共存时命令可能指向旧版本。遇到这种情况,用
python3(Mac/Linux)或py -3.12(Windows)指定版本号即可。
一个让学习效率翻倍的建议:多用交互式环境(REPL)。 在终端直接输入 python 回车,就进入一个"即输即得"的界面,提示符变成 >>>,敲一行代码立刻出结果,最适合边学边试。想退出就输入 exit() 回车。后面几节的内容,建议你一边读一边在里面敲一遍,比只看不敲强十倍。
环境就绪,写第一个程序。新建一个 hello.py 文件,输入:
print("Hello, AI Agent!")然后运行:
python hello.py屏幕上出现 Hello, AI Agent!,你的编程生涯正式开始了。print() 是 Python 最常用的函数,作用就是把内容打印到屏幕上,调试代码时全靠它。
顺便学会写注释。单行注释用 # 开头,解释器会直接忽略它,是写给"未来的自己"看的说明:
# 这是单行注释,解释器不会执行它print("你好,AI Agent!") # 也可以在代码后面加注释多行说明可以用三引号包裹,这叫文档字符串(docstring),通常放在文件、函数或类的开头,描述这段代码是干什么的:
"""这是文档字符串,用于说明下面这段代码的用途。"""print("Hello, World!") # 编程界的传统:第一个程序新手常见坑:注释里的中文没问题,但代码里的引号、括号必须是英文符号。很多人第一次报错,就是因为不小心输入了中文的括号或冒号,报错信息还看不懂。记住:代码用英文符号,注释随便。
编程的第一步,是理解变量。把变量想象成一个贴了标签的盒子:标签是变量名,盒子里装的是值,想换随时换。
name = "小明"# 字符串:一串文字age = 28# 整数height = 1.75# 浮点数:带小数is_student = False# 布尔值:True/False注意,Python 不需要声明类型——你直接赋值,它自己会判断。这种特性叫"动态类型",对新手极其友好,能少写很多废话。
变量命名有讲究。 名字只能由字母、数字、下划线组成,且不能以数字开头,也不能用 if、for 这类关键字。惯例是用小写字母加下划线分隔,比如 user_input、api_key,这叫 snake_case。给变量起个能看懂的名字,比任何注释都管用——三个月后回来看代码,api_key 一看就懂,a1、x2 会让你抓狂。
数字类型:int(整数)和 float(浮点数)。 运算和数学课一样:+ - * /,另有整除 // 和取余 %。Agent 开发里最典型的用法是算成本:大模型按 token 计费,你要把返回的 token 数换算成钱。
total_tokens = 1024cost = total_tokens / 1000 * 0.002# 算一次 API 调用的费用print(cost) # 0.002048字符串(str)是拼 prompt 的主力。 字符串本质是一串字符,支持"索引"——从 0 开始数位置,也支持负数从末尾数:
text = "hello agent"print(text[0]) # h,第 1 个字符print(text[-1]) # t,倒数第 1 个字符print(text[0:5]) # hello,切片:取第 0 到第 4 个字符切片是 [起点:终点],含头不含尾,这个规则记住一次,以后处处受用。它常用来截断超长的用户输入,保护你的 token 预算。
字符串还自带一堆方法,处理模型返回的文本时天天用:
raw = " Hello, Agent! "print(raw.strip()) # "Hello, Agent!",去掉首尾空白print(raw.upper()) # 全部转大写print(raw.lower()) # 全部转小写print("a,b,c".split(",")) # ['a', 'b', 'c'],按逗号拆成列表print("-".join(["a", "b"])) # "a-b",把列表拼回字符串split 和 join 是一对"拆"与"合":split 把字符串按分隔符拆成列表,join 把列表按分隔符合成字符串。处理 CSV 数据、拼接请求参数时经常用到。
字符串在 Agent 里的高频用法,是"判断"和"提取"。 判断用 in 和 startswith/endswith,比如判断用户输入是否包含某个关键词、是否以某个词开头:
user_input = "帮我查一下明天的天气"print("天气"in user_input) # True,包含"天气"print(user_input.startswith("帮我")) # True,以"帮我"开头提取则配合切片,从一段文本里截取指定位置的子串。大模型的返回结果往往是一大段文本,你要从中定位并截取关键信息,这套"定位 + 截取"能力就是基本功。真实 Agent 里处理工具返回的文本、解析模型输出,天天都在用。
类型转换很常用。 input() 拿到的用户输入永远是字符串,想当数字用必须转:
age = input("你几岁了?") # 输入 18,得到的是字符串 "18"age_num = int(age) # 转成整数 18print(age_num + 1) # 19int()、float()、str() 分别是转整数、转小数、转字符串的三件套。在 Agent 里,你经常要把模型返回的数字字符串转成数值去计算,或者把数字拼进提示词——类型搞混,程序就会报 TypeError。
布尔值(bool)只有两个:True 和 False。 比较运算会产出布尔值,比如 3 > 2 是 True,3 < 2 是 False。它常被用来做"开关",比如"是否启用重试""是否记录日志",后面你会经常看到。
除了上面这些基础类型,Agent 开发中还频繁用到几种"容器"类型:
["搜索", "计算器"]{"role": "user", "content": "你好"}("北京", "上海"){"a", "b"}list 详解:Agent 里到处是列表。 工具清单、消息历史、待办任务,全是 list。增删改查是基本功:
tools = ["搜索", "计算器"]tools.append("天气") # 末尾追加tools.remove("搜索") # 删除指定元素popped = tools.pop() # 弹出末尾元素,并拿到它print(tools) # ['计算器']print(len(tools)) # 1,长度print("天气"in tools) # False,判断是否包含list 也支持索引和切片,和字符串一样:tools[0] 取第一个,tools[1:] 取从第二个到末尾。注意 append 是"加到末尾",insert 可以插到指定位置,remove 按值删,pop 按位置删并返回被删的元素。先记住 append 和 pop 这对最常用的即可。
list 在 Agent 里的典型场景,是"消息历史"和"工具清单"。 每次对话,你都要把用户和模型的对话一条条追加到 messages 里,这就是 append 的主场:
messages = []messages.append({"role": "user", "content": "你好"})messages.append({"role": "assistant", "content": "你好!"})# 会话越长,列表越长,模型能记住的上下文越多print(len(messages)) # 2但消息太多会撑爆 token 预算,所以超限时要"丢最旧的"。实用技巧:用切片 messages[-10:] 只保留最后 10 条,或者用 pop(0) 弹出最早的一条。这种"滚动窗口"管理上下文的方式,是每个真实 Agent 都要处理的细节。
dict 详解:大模型消息格式就是它。 dict 是键值对集合,键像"抽屉的标签",值像"抽屉里的东西":
msg = {"role": "user", "content": "你好"}msg["content"] = "帮我写周报"# 改值msg["name"] = "小明"# 新增键值对del msg["name"] # 删除键print(msg.get("role")) # user,取不到返回 Noneprint(msg.get("age", 0)) # 0,取不到返回默认值遍历 dict 最常用 items(),一次拿到键和值:
for key, value in msg.items(): print(key, "=>", value)dict 和 JSON 是一对双胞胎。 大模型的输入输出都是 JSON 字符串,而 JSON 在 Python 里就是 dict 和 list 的组合。模型返回 {"content": "你好"},解析出来就是一个 dict。这也是为什么 dict 必须学扎实——它是你和大模型对话的"通用语言"。
嵌套结构很常见。 一个 dict 的值可以是 list,list 里又可以套 dict,就像消息列表:
messages = [ {"role": "system", "content": "你是一个乐于助人的助手"}, {"role": "user", "content": "帮我写一份周报"}]print(messages[0]["role"]) # system,先取第 0 个,再取 role 键这种 list 套 dict 的结构,你后面会天天见到。取值的口诀是"一层层剥洋葱":先 [下标] 再 ["键名"],按顺序剥到目标为止。
tuple 和 set 的适用场景。 tuple 不可变,适合放"不该被改"的数据,比如模型的 (温度, 最大长度) 配置;函数返回多个值时也常用 tuple 打包。set 自动去重,适合"判断元素在不在集合里"和"去掉重复项":
ids = ["a", "b", "a", "c"]unique = set(ids) # {'a', 'b', 'c'},自动去重print("b"in unique) # True,成员判断极快f-string:拼 prompt 的最优雅姿势。 前面说过字符串是拼 prompt 的主力,而 f-string 是 Python 3.6 之后最优雅的拼接方式:在字符串前加一个 f,用花括号嵌入变量:
assistant_name = "智能客服"version = "2.0"prompt = f"你是{assistant_name},当前版本{version},请用简洁专业的语气回答用户问题。"print(prompt)输出:你是智能客服,当前版本2.0,请用简洁专业的语气回答用户问题。
在 Agent 开发里,你经常要动态拼 system prompt——把角色设定、用户输入、上下文组合成一段完整指令,f-string 就是你的主力工具。记住这个写法,后面天天用。
新手常见坑:f-string 里的花括号是插值用的,如果提示词里本身想写花括号(比如 JSON 模板),要写成 {{ 和 }} 转义。另外 dict 用 [] 取值时键不存在会直接报 KeyError 崩溃,所以不确定键是否存在时,优先用 .get()。
光有数据还不够,代码需要逻辑。控制流就是给代码装"大脑"——会判断,会循环。
先认识比较运算符和逻辑运算符。 比较运算符有 ==(等于)、!=(不等于)、>、<、>=、<=,它们产出的都是布尔值。逻辑运算符有三个:and(并且)、or(或者)、not(取反)。
code = 200if code == 200and code != 429: print("请求成功")if code == 200or code == 201: print("成功类状态码")ifnot code == 429: print("没有触发限流")在 Agent 里,你经常要判断"API 返回状态码":200 代表成功,429 代表限流,500 代表服务器错误。用 if 分支分别处理,就是最典型的实战场景。
条件判断用 if/elif/else,语法接近自然语言:
user_input = input("你今天心情好吗?请输入 好 或 不好:")if user_input == "好": print("太好了,保持好心情!")elif user_input == "不好": print("没关系,写点代码放松一下。")else: print("我没听懂,请重新输入。")注意两点:条件后面要有冒号;代码块靠缩进(通常 4 个空格)区分层级。Python 用缩进代替了其他语言的花括号,这也是它读起来清爽的原因。
新手常见坑:缩进是新手第一周最大的报错来源。混用 Tab 和空格、缩进层级不一致,都会报 IndentationError。建议统一用 4 个空格,VS Code 默认就是,别手动敲 Tab。
if 的实战场景:判断用户输入、判断 API 返回状态码。 比如一个简单的"意图路由"——根据用户输入决定调用哪个工具:
user_input = "帮我查一下明天北京的天气"if"天气"in user_input: tool = "weather"elif"翻译"in user_input: tool = "translate"else: tool = "chat"print(f"路由到工具:{tool}")这就是 Agent 里"意图识别"的最朴素版本:关键词命中就分派到对应工具。真实产品会用大模型做意图分类,但"先判断再执行"的思路一模一样。注意 in 这个运算符:判断一个字符串是否包含另一个字符串,返回布尔值,写起来非常自然。
状态码判断的完整写法。 大模型和各类 API 都会返回状态码,200 表示成功,429 表示限流,500 表示服务器错误。你需要在代码里根据状态码决定下一步动作:
status = 429if status == 200: print("正常处理返回结果")elif status == 429: print("触发限流,请等待后重试")elif status >= 500: print("服务器错误,稍后再试")else: print(f"未知状态码:{status}")这种"状态码 → 对应处理"的分支逻辑,几乎每个调用外部服务的函数里都有。注意 elif 是"else if"的缩写,可以连续写多个,从上到下依次判断,命中一个就停止。
循环有两种:for 循环用来遍历集合,while 循环在条件成立时反复执行。
for 循环 + range():遍历是 Agent 的高频操作。 range() 可以生成一串数字:
for i in range(3): # 0, 1, 2 print(f"第 {i + 1} 次尝试")for i in range(1, 10, 2): # 从 1 到 9,步长 2 print(i) # 1 3 5 7 9range(n) 生成 0 到 n-1,range(起点, 终点, 步长) 可以定制。配合列表遍历,比如逐个加载工具列表:
tools = ["搜索", "计算器", "天气查询", "翻译"]for tool in tools: print(f"正在加载工具:{tool}")break 和 continue 是对循环的"遥控器"。 break 立刻跳出整个循环,continue 跳过本次、进入下一次:
for i in range(10):if i == 3:continue# 跳过 3,不打印if i == 7:break# 到 7 就停 print(i) # 输出 0 1 2 4 5 6实战里,break 常用于"找到第一个符合条件的就收手",continue 常用于"跳过无效数据"。比如遍历消息列表,跳过 role 为 system 的,遇到第一条 user 消息就处理。
while 循环:常用于"轮询任务状态"。 很多模型平台支持异步任务:你提交一个任务,拿到一个 task_id,然后反复查询它的状态,直到变成"完成"。这就是 while 的典型战场:
status = "pending"attempt = 0while status notin ("completed", "failed") and attempt < 5: print(f"第 {attempt + 1} 次查询任务状态:{status}")# 真实项目里这里会调用查询 API status = "completed"# 模拟:任务完成了 attempt += 1print("最终状态:", status)注意 attempt += 1 不能漏,否则条件永远成立,程序会陷入死循环。这是新手最容易踩的坑。另一个技巧是给 while 加"次数上限",防止无限轮询——真实项目里还会配合超时时间一起用。
列表推导式:一行代码完成"遍历 + 筛选 + 生成新列表"。
tools = ["搜索", "计算器", "天气查询", "翻译"]available = [t for t in tools if"查询"notin t]print(available) # ['搜索', '计算器', '翻译']这段代码读起来像英语:"for t in tools if not 查询"——把符合条件的 t 收集成新列表。带 if 条件是最常见用法;嵌套推导式可以对二维数据做处理,比如把消息列表里的 content 全取出来:
messages = [ {"role": "user", "content": "你好"}, {"role": "assistant", "content": "你好!有什么可以帮你?"}]contents = [m["content"] for m in messages]print(contents) # ['你好', '你好!有什么可以帮你?']字典推导式: 类似的语法可以快速生成 dict:
scores = {"搜索": 3, "计算器": 2}double = {k: v * 2for k, v in scores.items()}print(double) # {'搜索': 6, '计算器': 4}在 Agent 里,列表推导式常用于"从工具清单里筛选可用工具""过滤无效消息""批量提取字段"这类场景。写多了你会发现,它能让你代码短一半,逻辑还一目了然。
函数是把一段逻辑打包起来、起个名字、随时调用的机制。写 Agent 代码时,你会把"处理输入""调用模型""解析结果"分别写成函数,就像搭积木。
定义函数用 def 关键字:
defgreet(name):returnf"你好,{name}!"print(greet("小红")) # 你好,小红!参数有四种玩法。 位置参数按顺序传;默认参数给参数一个兜底值,调用时不传就用默认值;关键字参数让你按名字传参,不用记顺序:
defbuild_messages(user_input, system_prompt="你是一个乐于助人的助手", max_len=500):if len(user_input) > max_len: user_input = user_input[:max_len] + "..."return [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_input} ]# 只传必填参数msgs = build_messages("帮我写一份周报")# 用关键字参数覆盖默认值msgs = build_messages("帮我写一份周报", system_prompt="你是一个资深职场顾问")这个函数就是"调用大模型前的参数处理":截断超长输入、组装成标准消息格式。默认参数让调用方可以少写很多重复配置,这也是框架代码里最常见的参数设计。
新手常见坑:默认参数不要用可变对象(比如 [] 或 {})。Python 只在定义函数时创建一次默认值,多次调用会共享同一个列表,改一处全变。想用"空列表"做默认值,写成 None,函数内部再判断。
*args 和 **kwargs: 用来接收"不定数量的参数"。*args 把多余的按位置传的参数打包成元组,**kwargs 把多余的关键字参数打包成 dict。在框架源码里很常见,比如日志函数、事件回调:
deflog_event(event, **kwargs): print(f"事件:{event},附加信息:{kwargs}")log_event("api_call", model="gpt-4o", cost=0.01)# 事件:api_call,附加信息:{'model': 'gpt-4o', 'cost': 0.01}返回值: return 把结果交还给调用方。没有 return 的函数返回 None。一个函数可以返回多个值,Python 会打包成元组:
defanalyze(text):return len(text), text.upper() # 返回两个值length, upper_text = analyze("hello")print(length, upper_text) # 5 HELLO变量作用域: 函数里定义的变量是"局部"的,外面看不到;函数外定义的叫"全局"变量,函数内可以读,但想修改要加 global 声明。新手阶段记住一句话:尽量用参数传数据、用 return 拿结果,少动全局变量,代码会干净很多。
count = 0# 全局变量defincrement():global count # 声明要修改全局变量 count += 1increment()print(count) # 1不过在实际的 Agent 项目里,你很少会用 global,因为"共享状态"这件事,类和对象已经帮你解决得更好(下一节就讲)。这里只要理解"函数内部的变量是封闭的"这个原则,避免写出"在函数里改全局变量导致到处出问题"的 bug 就行。判断一个变量是局部还是全局,就看它有没有在函数里被赋值——没赋值的就是从外面读的。
lambda 详解: 匿名函数,不需要名字,常用于配置里传简短的逻辑。语法是 lambda 参数: 表达式:
add = lambda x, y: x + yprint(add(1, 2)) # 3lambda 在框架配置中很常见,看到 lambda x: 表达式 这种写法,理解成"一个小型无名函数"即可,不用怕。它适合"一句话能写完"的逻辑,逻辑复杂了就该用 def 定义有名函数。
高阶函数:map、filter、sorted 的 key。 高阶函数就是"接收函数作为参数的函数"。map 对每个元素做变换,filter 按条件筛选,sorted 配合 key 自定义排序规则:
nums = [1, 2, 3, 4]print(list(map(lambda x: x * 2, nums))) # [2, 4, 6, 8]print(list(filter(lambda x: x > 2, nums))) # [3, 4]tools = ["搜索", "计算器", "天气查询"]sorted_tools = sorted(tools, key=lambda t: len(t))print(sorted_tools) # ['搜索', '计算器', '天气查询']注意 map 和 filter 的结果要包一层 list() 才能打印出列表,这是 Python 的"惰性求值"特性,先混个眼熟。sorted 的 key 参数尤其实用:按长度、按价格、按优先级排序,一行搞定。
实战:用函数组装 Agent 的调用流程。 真实 Agent 里,一次对话要经历"处理输入 → 组装消息 → 调用模型 → 解析结果"好几个环节。把它们拆成独立函数,每个函数只干一件事,代码就像流水线一样清晰:
defbuild_messages(user_input, system_prompt="你是一个乐于助人的助手"):return [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_input}, ]defcall_model(messages):# 真实项目里这里是 openai 调用return'{"choices": [{"message": {"content": "你好!"}}]}'defparse_content(raw):import jsonreturn json.loads(raw)["choices"][0]["message"]["content"]msgs = build_messages("帮我写一份周报")raw = call_model(msgs)print(parse_content(raw)) # 你好!函数化的好处是:同一段逻辑只写一次,换个参数就能复用,改一处全局生效。你在看 LangGraph 或 OpenAI SDK 的示例代码时,会反复见到这种"处理 + 组装"的函数——几乎每个真实 Agent 都有这么几个"处理函数",先把这种组织代码的感觉建立起来。
函数打包的是逻辑,类打包的是"数据 + 逻辑"。类可以理解成一张设计图纸,对象是按照图纸造出来的具体产品。
先看一个最典型的例子:封装一个 LLMClient 类。
classLLMClient:def__init__(self, api_key, model="gpt-4o-mini"): self.api_key = api_key self.model = modeldefchat(self, messages):# 真实项目里,这里会调用大模型 APIreturnf"[{self.model}] 收到 {len(messages)} 条消息"client = LLMClient(api_key="sk-xxxx", model="gpt-4o-mini")result = client.chat([{"role": "user", "content": "你好"}])print(result) # [gpt-4o-mini] 收到 1 条消息拆解一下:__init__ 是构造函数,创建对象时自动执行,用来初始化数据;self 代表"这个对象自己",类里的每个方法第一个参数都是它,通过它可以访问对象的属性(如 self.api_key)和其他方法。
类属性与实例属性: 写在 init 里、以 self. 开头的叫实例属性,每个对象各有一份;直接写在类体里的叫类属性,所有对象共享一份。比如 API 的基础地址适合放类属性,api_key 适合放实例属性。
实例方法: 第一个参数是 self 的方法,通过对象调用,能访问对象的数据。这是类里最常见的成员。后面你还会遇到 @staticmethod(静态方法,不依赖对象数据)和 @classmethod(类方法),先混个眼熟,用到再查。
继承:子类复用父类。 你可以写一个"基础客户端",再派生出"带记忆的客户端""带工具的客户端",子类自动拥有父类的全部能力,还能改写:
classBaseClient:def__init__(self, api_key): self.api_key = api_keydefchat(self, messages):returnf"调用模型,收到 {len(messages)} 条消息"classMemoryClient(BaseClient):def__init__(self, api_key, memory_size=10): super().__init__(api_key) # 调用父类构造函数 self.memory_size = memory_sizedefchat(self, messages): result = super().chat(messages) # 复用父类逻辑return result + f",记忆容量 {self.memory_size}"client = MemoryClient("sk-xxx")print(client.chat([{"role": "user", "content": "hi"}]))# 调用模型,收到 1 条消息,记忆容量 10super() 就是"父类"的引用,子类想调用父类的方法就靠它。继承的价值在于"复用":公共能力写在父类,特殊能力在子类里叠加,代码不会越写越臃肿。
魔术方法:str 和 repr。 名字前后带双下划线的方法,Python 会在特定时机自动调用。str 定义"打印这个对象时显示什么":
classTool:def__init__(self, name, enabled=True): self.name = name self.enabled = enableddef__str__(self):returnf"工具({self.name}, 启用={self.enabled})"t = Tool("搜索")print(t) # 工具(搜索, 启用=True),而不是一串看不懂的内存地址没有 str 时打印对象,你会看到 <__main__.Tool object at 0x...> 这种地址,毫无信息量。定义 str 后,调试日志瞬间变得可读。repr 类似,主要给开发者调试用,先记住 str 就够。
实战:完整版 LLMClient(含重试、日志)。 把前面学的都揉进去:
import timeclassLLMClient:def__init__(self, api_key, model="gpt-4o-mini", max_retries=3): self.api_key = api_key self.model = model self.max_retries = max_retries self.call_count = 0# 记录调用次数defchat(self, messages):for attempt in range(self.max_retries):try:# 真实项目里这里是 requests.post 或 openai 调用 self.call_count += 1 print(f"[日志] 第 {attempt + 1} 次调用 {self.model}")returnf"[{self.model}] 回复:收到 {len(messages)} 条消息"except Exception as e: print(f"[日志] 调用失败:{e},准备重试") time.sleep(1) # 退避一下再试return"调用多次失败,请稍后再试"client = LLMClient(api_key="sk-xxx")print(client.chat([{"role": "user", "content": "你好"}]))print(f"累计调用次数:{client.call_count}")为什么 Agent 开发中要用类? 因为一个完整的 LLM 客户端需要封装很多东西:API 密钥、模型名、请求重试、日志记录、成本统计。用类把这些状态和行为绑定在一起,调用方只需要 client.chat(...),不用关心内部细节。这就是"封装"的价值——这也是你在看 LangChain、LangGraph 源码时,会看到大量类的根本原因。
你可能想问:这些用函数也能实现,为什么非要类?区别在于"状态"。函数是"用完即走"的,而类可以长期持有状态——比如 client 记住你的 api_key 和 model,你每次调用都不用重新传。一个 Agent 项目里,模型客户端、工具管理器、记忆存储,几乎都是这样用类封装的。理解了这一点,你读框架源码时就不会再发怵。
调用外部 API 时,有一件事你迟早会遇到:报错。网络抖动、接口限流、参数不合法,任何一个环节出错,程序都可能当场崩溃。
先认识常见的异常类型。 Python 内置了各种异常,各管一摊:
看到报错信息末尾的异常类型名,你就能猜个大概,这是排查问题的第一步。
try/except/else/finally 完整结构。 除了 try 和 except,还有两个可选块:
try: num = int("abc") # 故意制造错误except ValueError as e: print(f"转换失败:{e}") # 出错时执行else: print("没有出错才执行这里")finally: print("无论是否出错,都执行这里")else 块在 try 没出错时执行,finally 块无论如何都会执行——比如"无论成败都要关闭连接、释放资源"。
捕获多个异常: 可以写多个 except 分支,分别处理;也可以把多个异常类型放进一个元组:
try: result = 10 / 0except (ValueError, TypeError): print("参数类错误")except ZeroDivisionError as e: print(f"除零错误:{e}")except Exception as e: # 兜底,捕获所有其他异常 print(f"未知错误:{e}")自定义异常: 当内置异常不够表达业务语义时,可以自己定义。比如"余额不足""超过调用限额":
classQuotaExceededError(Exception):passdefcall_model():raise QuotaExceededError("本月调用额度已用完")try: call_model()except QuotaExceededError as e: print(f"业务错误:{e},请充值或等额度重置")实战:带重试机制的 API 调用函数。 大模型 API 经常限流(429)、超时(TimeoutError),一个健壮的函数应该自动重试:
import timedefcall_api_with_retry(url, max_retries=3, delay=1):for attempt in range(max_retries):try:# 真实项目里这里是 requests.get 或 openai 调用if url == "":raise ValueError("URL 不能为空")if attempt == 0:raise ConnectionError("模拟网络抖动")return"请求成功"except (ConnectionError, TimeoutError) as e: print(f"第 {attempt + 1} 次失败:{e},{delay} 秒后重试") time.sleep(delay)except ValueError as e: print(f"参数错误,不重试:{e}")returnNonereturn"重试多次仍失败"print(call_api_with_retry("https://api.example.com"))注意这里的技巧:ValueError 属于"改了也白改"的参数错误,直接返回不重试;ConnectionError 属于"过会儿可能就好了"的临时错误,值得重试。区分这两类,是写健壮代码的关键。
在 Agent 实战中,异常处理是必修课。 大模型 API 经常限流、超时,一个健壮的 Agent 必须能捕获这些异常并给出降级方案——重试一次、换备用模型、告诉用户稍后再试。可以这么说:没有异常处理的 Agent,上线第一天就会崩给你看。
最后一个概念,也是很多新手最容易懵的地方:异步编程。先用一个比喻讲清楚为什么需要它。
假设你的 Agent 要同时调用三个 API:查天气、查新闻、算数据。如果一个个来,每个耗时 1 秒,总共要 3 秒;如果能同时发起三个请求,总耗时还是 1 秒左右。异步编程,就是让代码具备这种"同时开工"的能力。
打个比方:同步代码像排队打饭,一个窗口一个人,前面的打完才轮到你;异步代码像自助餐厅,多个窗口同时出餐,你手里同时排着好几份。对 Agent 来说,它要调用大模型、查工具、读文件,全是耗时操作,异步几乎是必备技能。
同步 vs 异步对比:
async/await 详解。 Python 里用 async/await 实现异步。async def 定义的是协程函数(可以理解为"可暂停的任务");await 表示"在这里等结果,但等待期间可以去干别的":
import asyncioasyncdeffetch_weather(city):await asyncio.sleep(1) # 模拟网络请求耗时 1 秒returnf"{city}:晴,25℃"asyncdeffetch_news():await asyncio.sleep(1)return"今日要闻:AI 技术正在改变软件开发"asyncdefmain(): results = await asyncio.gather(fetch_weather("北京"), fetch_news()) print(results)asyncio.run(main())gather 与 create_task。 asyncio.gather 把多个协程打包并发执行,等全部完成返回结果列表,是最常用的写法。create_task 则把协程"挂到后台"运行,适合"发出去就不管,稍后再收结果"的场景:
asyncdefmain(): task = asyncio.create_task(fetch_weather("上海")) # 后台启动# 这里可以干别的活 result = await task # 需要时再等结果 print(result)实战:并发调用多个 API。 假设 Agent 一次任务要查三个城市天气,同步要 3 秒,异步只要 1 秒:
import asyncioasyncdeffetch_weather(city):await asyncio.sleep(1) # 模拟网络请求returnf"{city}:晴"asyncdefmain(): cities = ["北京", "上海", "广州"] results = await asyncio.gather( *[fetch_weather(c) for c in cities] # * 把列表展开成多个参数 )for r in results: print(r)asyncio.run(main())# 北京:晴# 上海:晴# 广州:晴注意 *[fetch_weather(c) for c in cities] 里的星号:它把列表"拆开"成一个个独立参数传给 gather,这是 Python 里很常用的解包技巧,配合列表推导式使用,代码非常简洁。
什么时候该用异步? 三条判断标准:一是有多个独立的耗时操作要并发;二是这些操作之间没有先后依赖;三是操作主要是"等网络"而不是"算 CPU"。满足这三条,就用异步。如果只是单次调用、或者操作之间有强依赖,同步就够了,别为了异步而异步。
本篇你只需要建立这个认知:看到 async/await 知道是在做并发,能读懂示例即可。等你在真实项目里用上几次,自然就越写越顺了。
Python 之所以强大,一半功劳要归给"别人写好的代码"——模块。模块就是别人打包好的功能,导入进来就能用,不用自己从零写。
import 的三种写法。 最常用的是 import 模块名,然后模块名.函数名 调用:
import jsondata = json.loads('{"content": "你好"}')print(data["content"]) # 你好from 模块 import 名字 可以直接导入某个函数,调用时不用带模块名:
from datetime import datetimenow = datetime.now()print(now) # 2026-08-16 12:00:00 之类as 是起别名,名字太长或容易撞车时用:
import json as jprint(j.loads('{"a": 1}')) # {'a': 1}常用标准库:json、os、datetime。 标准库是 Python 自带的,不用安装。json 负责 JSON 和 dict 互转;os 负责和操作系统打交道(读环境变量、拼文件路径);datetime 处理时间:
import osimport jsonfrom datetime import datetime# 读环境变量里的 API 密钥(比硬编码安全)api_key = os.getenv("OPENAI_API_KEY", "未设置")print(api_key)# 给消息加时间戳now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")print(f"[{now_str}] 收到用户消息")pip 安装第三方库。 标准库之外还有海量第三方库,用 pip 安装:
pip install requestspip install openai装完就能 import 使用。requests 是 HTTP 请求神器,openai 是官方 SDK。注意:装库前建议先建一个虚拟环境,把不同项目的依赖隔离开,避免版本冲突。这一步新手常忽略,等踩坑了再回来补。
python -m venv venv # 创建虚拟环境source venv/bin/activate # Mac/Linux 激活(Windows 用 venv\Scripts\activate)pip install requests openai # 在虚拟环境里装库虚拟环境可以理解成"每个项目一个独立的行李箱":A 项目装 requests 2.31,B 项目装 requests 2.28,互不干扰。没有它,两个项目一升级依赖,就可能互相把对方搞崩。等你开始做真实项目,几乎每个都会建一个虚拟环境,现在先建立这个习惯。
os 模块还有一个高频用法:拼接文件路径。 不同操作系统的路径分隔符不一样(Windows 是反斜杠,Mac/Linux 是正斜杠),用 os.path.join 可以自动适配,避免"代码在 Mac 能跑、到 Windows 就报错"的尴尬:
import osconfig_path = os.path.join("config", "agent.yaml")print(config_path) # Mac 输出 config/agent.yaml实战:用 json 模块解析大模型返回结果。 大模型 API 返回的是一段 JSON 字符串,解析是每天的常规操作:
import json# 模拟大模型返回的原始 JSON 字符串raw_response = '{"choices": [{"message": {"role": "assistant", "content": "你好,我是智能助手"}}]}'data = json.loads(raw_response) # 字符串 -> dictcontent = data["choices"][0]["message"]["content"] # 层层取print(content) # 你好,我是智能助手# 反向操作:把 dict 转成 JSON 字符串(发给 API 时用)payload = {"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "hi"}]}print(json.dumps(payload, ensure_ascii=False)) # 中文不转义,方便阅读json.loads 是"字符串变 dict",json.dumps 是"dict 变字符串",这一进一出,就是你和模型 API 之间的"翻译官"。ensure_ascii=False 保证中文原样输出,不然会变成 \uXXXX 的转义符。
新手常见坑:json.loads 遇到格式不对的字符串会抛 JSONDecodeError。解析前最好先确认字符串是完整合法的 JSON,或者用 try/except 包一层。
前面学的都是零件,现在把它们组装起来。下面这个"简易智能客服",把变量、列表、字典、循环、推导式、函数、类、异常处理、异步全部用上。先整体看一遍,再逐行讲解。
import asyncioimport jsonimport timeclassLLMClient:"""模拟大模型客户端"""def__init__(self, api_key, model="gpt-4o-mini", max_retries=2): self.api_key = api_key self.model = model self.max_retries = max_retriesdefchat(self, messages):for attempt in range(self.max_retries):try:# 真实项目里这里是 openai 调用 user_msg = messages[-1]["content"]returnf"我是{self.model},已收到你的问题:{user_msg}"except Exception as e: print(f"调用失败:{e},重试中...") time.sleep(0.5)raise ConnectionError("多次调用失败")defbuild_messages(user_input, system_prompt="你是智能客服小助手"):return [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_input}, ]defparse_response(raw):"""解析模型返回的 JSON""" data = json.loads(raw)return data["choices"][0]["message"]["content"]defroute_intent(user_input, keywords):"""根据关键词路由到对应回复模板"""for intent, words in keywords.items():if any(w in user_input for w in words):return intentreturn"chat"asyncdeffetch_order_status(order_id):"""模拟异步查询订单状态"""await asyncio.sleep(0.5)returnf"订单 {order_id} 已发货,预计明天送达"asyncdefmain(): client = LLMClient(api_key="sk-xxx") keywords = {"order": ["订单", "物流", "快递"],"refund": ["退款", "退货"], }whileTrue: user_input = input("请问有什么可以帮您?(输入 quit 退出)")if user_input == "quit": print("感谢使用,再见!")break intent = route_intent(user_input, keywords) print(f"[路由] 识别意图:{intent}")if intent == "order":# 并发查询多个订单 results = await asyncio.gather( fetch_order_status("A001"), fetch_order_status("A002") )for r in results: print(r)else: messages = build_messages(user_input) raw = client.chat(messages) print(f"[模型] {raw}")asyncio.run(main())逐行讲解:
运行这个程序,你会看到一次完整的"输入 → 路由 → 并发查询/模型回复"流程。虽然离生产级 Agent 还很远,但它把本篇所有知识点串成了一条线:你现在看到的每一个零件,将来在 LangGraph、OpenAI SDK 的代码里都会以更复杂的形式再次出现。
动手挑战:试着给这个客服加上"退款"分支的异步处理,或者把 LLMClient 的返回改成 JSON 字符串,再用 parse_response 解析。改通的那一刻,你就真的入门了。
回顾一下,这一篇你掌握了:
这些就是 Agent 开发中出镜率最高的 Python 语法。但"看懂"和"会写"之间,隔着一个关键动作:动手敲。
给你留四个练习,建议在交互环境或新建的 .py 文件里完成:
四个练习做完,你对本篇内容的掌握程度会比只读一遍高出一大截。编程是"手上功夫",不是"眼上功夫"——看懂不算会,敲出来才算。这些语法已经足够你动手写出第一个 Agent 脚本了。
今天你写了几个练习?评论区打卡,卡在哪一步我们一起解决,关注我们,一起搞定 AI Agent 开发。