从 0 到 1:用一个最小 Python Agent,搞懂 AI 对话程序的基本原理 如果你是第一次接触 AI 编程,这篇文章会带你用一个非常小的项目,理解”AI 对话程序”到底是怎么工作的。
这个项目不复杂,但它覆盖了最核心的一条链路:读取配置 → 调用模型 API → 拿到回复 → 在终端输出。
项目地址:https://git.liupx.com/study/openai_demo_first 更多信息(代码、更新、使用说明)可以访问这里。
1. 这篇文章你会学到什么 读完后,你应该能回答这几个问题:
什么是”OpenAI 兼容 API”
为什么要在 .env 里放 API Key 和 BASE_URL
AI 回复为什么能”一个字一个字蹦出来”(流式输出)
temperature、top_p 这些参数到底在控制什么
一个最小 AI Agent 的执行流程是什么
2. 先建立一个最小认知模型 先别看代码,先理解 4 个核心概念。
2.1 模型(Model) 你可以把模型理解成一个超大号文本补全器 。 你给它上下文,它预测下一段最合理的文本。
1 2 3 4 5 输入: "今天天气真" 模型预测: "好啊" / "糟糕" / "不错" ... 输入: "Python是一门" 模型预测: "编程语言" / "很流行的语言" / "简洁的语言" ...
2.2 消息(Messages) 聊天接口不是只传一句话,而是传一个消息列表 。每条消息有角色和内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ┌─────────────────────────────────────────────┐ │ Messages 列表 │ ├─────────────────────────────────────────────┤ │ ┌─────────────────────────────────────┐ │ │ │ role: "system" │ │ │ │ content: "你是一个严谨的学习教练" │ │ │ └─────────────────────────────────────┘ │ │ ┌─────────────────────────────────────┐ │ │ │ role: "user" │ │ │ │ content: "什么是Python?" │ │ │ └─────────────────────────────────────┘ │ │ ┌─────────────────────────────────────┐ │ │ │ role: "assistant" │ │ │ │ content: "Python是一门编程语言..." │ │ ← 多轮对话时 │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────┘
三种角色说明:
system:给模型设定角色和规则(像导演给演员的剧本要求)
user:用户问题(像你问老师的问题)
assistant:模型历史回答(多轮对话时需要带上,让模型记住上下文)
2.3 API(程序访问模型的方式) 我们不是在本地跑大模型,而是通过 HTTP 接口调用远程模型服务。
1 2 3 4 5 6 7 8 graph LR A[你的程序] -- HTTP请求 --> B[模型服务API] B -- 返回结果 --> A C[.env文件] -->|提供密钥和地址| A style A fill:#e1f5ff style B fill:#fff4e1 style C fill:#e8f5e9
这个项目使用的是 OpenAI Python SDK,但可以连”OpenAI 兼容接口”(如 DeepSeek、阿里通义千问等)。
2.4 Token(模型处理文本的基本单位) 模型按 token 处理文本,不是按”字数”。
Token 是什么?
1 个 token ≈ 0.75 个英文单词,或约 1-2 个汉字
模型把文本切分成小片段(token)来理解和生成
1 2 3 4 5 6 7 8 9 10 11 示例:这句话有多少个 token? "我爱学习编程" 可能的切分方式: ┌────────────────────────────┐ │ 我 │ 爱 │ 学习 │ 编程 │ │ token1 │ token2 │ token3 │ token4 │ └────────────────────────────┘ 约 4-6 个 token(取决于具体分词器)
max_tokens 控制的是”最多生成多少 token”,不是”最多多少个汉字”。
3. 这个项目做了什么 项目里有两个入口:
它们的核心事情完全一致:调用 client.chat.completions.create(...),然后把结果打印出来。
4. 运行前准备:为什么要 .env 配置文件示例(.env.example )是:
1 2 OPENAI_API_KEY=your_api_key BASE_URL=https://your-openai-compatible-endpoint/v1
原因很简单:
配置项
作用
为什么需要
OPENAI_API_KEY
身份凭证
没有它服务端不会让你调用
BASE_URL
接口地址
告诉 SDK 请求发到哪个模型服务
为什么用 .env 文件?
1 2 3 4 5 6 7 8 9 10 11 ┌─────────────────────────────────────────┐ │ 不推荐:直接写死在代码里 │ │ api_key = "sk-xxxxx" ← 容易泄露! │ └─────────────────────────────────────────┘ ┌─────────────────────────────────────────┐ │ 推荐:使用 .env 文件 │ │ 1. 安全:不会被提交到 Git │ │ 2. 灵活:不同环境用不同配置 │ │ 3. 规范:业界标准做法 │ └─────────────────────────────────────────┘
把密钥放在 .env,比直接写死在代码里安全得多,也便于切换环境。
5. 主流程拆解(最重要) 5.1 整体流程图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 flowchart TD A[程序启动] --> B[加载 .env 文件<br/>load_dotenv] B --> C[读取配置<br/>OPENAI_API_KEY<br/>BASE_URL] C --> D{配置检查} D -->|缺少API Key| E[错误提示并退出] D -->|缺少BASE_URL| E D -->|配置正常| F[创建客户端<br/>OpenAI client] F --> G[组装消息<br/>messages列表] G --> H[发起请求<br/>chat.completions.create] H --> I{是否流式?} I -->|是 stream=True| J[循环读取 chunk<br/>实时打印] I -->|否 stream=False| K[等待完整结果<br/>一次性打印] J --> L[输出完成] K --> L style E fill:#ffebee style L fill:#e8f5e9 style J fill:#e3f2fd
5.2 核心代码讲解(最小可运行版本) 这是一个完整的最小示例,约 30 行代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import osfrom dotenv import load_dotenvfrom openai import OpenAIload_dotenv() api_key = os.getenv("OPENAI_API_KEY" ) base_url = os.getenv("BASE_URL" ) client = OpenAI(api_key=api_key, base_url=base_url) messages = [ {"role" : "user" , "content" : "帮我设计一个早起计划" } ] 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="" , flush=True )
你会发现,AI 应用的第一版往往就这几步。
6. 为什么会”流式输出” 6.1 原理解析 在 main.py 中,请求里用了:
这意味着服务端会把回答切成多个小片段返回。
非流式 vs 流式对比:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌────────────────────────────────────────────────────────────┐ │ 非流式输出 (stream=False) │ ├────────────────────────────────────────────────────────────┤ │ 等待... 等待... 等待... │ │ [10秒后] │ │ "完整的一段话一次性显示出来" │ │ │ │ 用户体验:等待时间较长,不知道是否在处理 │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ 流式输出 (stream=True) │ ├────────────────────────────────────────────────────────────┤ │ "完" -> "整" -> "的" -> "一" -> "段" -> "话" │ │ 一个字一个字实时显示出来 │ │ │ │ 用户体验:即时反馈,更流畅,像真人对话 │ └────────────────────────────────────────────────────────────┘
6.2 代码实现 流式处理的关键代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 stream = client.chat.completions.create( model="deepseek-chat" , messages=[{"role" : "user" , "content" : "你好" }], stream=True , ) for chunk in stream: if not chunk.choices: continue delta = chunk.choices[0 ].delta.content if delta: print (delta, end="" , flush=True )
所以你看到的效果就是:
不是等整段回答全部生成完才显示
而是边生成边显示,体验更像实时对话
7. 参数到底在调什么(面向新手版) main_cli.py 支持几个常见参数:
7.1 参数速查表
参数
作用
取值范围
建议值
--model
使用哪个模型
字符串
deepseek-chat
--system
给模型设定角色
任意文本
根据场景设置
--temperature
随机性/发散程度
0.0 ~ 2.0
0.2~0.8
--top-p
核采样控制
0.0 ~ 1.0
0.9 左右
--max-tokens
最多生成多少token
正整数
视需求而定
--no-stream
关闭流式输出
标志位
需要完整结果时使用
7.2 Temperature 详解 Temperature 是什么?
Temperature 控制模型输出的随机性和多样性。想象一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Temperature = 0 (完全确定性) ┌────────────────────────────────┐ │ 像"背诵课文" │ │ 每次回答几乎完全相同 │ │ 适合:需要精确、稳定的答案 │ └────────────────────────────────┘ Temperature = 0.7 (适中随机) ┌────────────────────────────────┐ │ 像"正常交谈" │ │ 有一定变化,但保持逻辑一致 │ │ 适合:日常对话、创意任务 │ └────────────────────────────────┘ Temperature = 1.5 (高随机性) ┌────────────────────────────────┐ │ 像"头脑风暴" │ │ 非常有创意,但可能不太连贯 │ │ 适合:需要大量创意的场景 │ └────────────────────────────────┘
7.3 实际效果对比 使用不同的 temperature 调用同一个问题:
1 2 3 4 5 6 7 python src/agent/main_cli.py "用一句话介绍Python" --temperature 0.2 python src/agent/main_cli.py "用一句话介绍Python" --temperature 0.9
7.4 新手建议
先只改 prompt 和 --system
temperature 先从 0.2 到 0.8 小范围试
不要一开始同时乱调多个参数 ,不然很难判断变化原因
需要稳定答案时用低 temperature,需要创意时用高 temperature
8. 常见报错怎么理解 8.1 “未检测到 OPENAI_API_KEY” 现象:
1 未检测到 OPENAI_API_KEY。请在 .env 文件中配置后重试。
原因和解决:
.env 文件不存在或没有配置 OPENAI_API_KEY
.env 文件不在项目根目录
检查步骤:确认 .env 文件存在,内容格式正确
8.2 “未检测到 BASE_URL” 现象:
1 未检测到 BASE_URL .env 文件中配置后重试。
原因和解决:
接口地址缺失,SDK 不知道请求发去哪
确保在 .env 中配置了 BASE_URL
8.3 连接失败 / 429 / 配额不足 现象:
1 调用失败:HTTPSConnectionPool(...) / 429 Client Error
可能原因:
问题类型
检查项
服务地址不可达
BASE_URL 是否正确,网络是否通畅
网络或代理问题
是否需要配置代理,防火墙是否拦截
额度不足或限流
账户是否有足够配额,是否触限流
排查顺序:
先检查配置(.env 文件)
再检查网络(是否能访问 API 地址)
最后看平台账单和配额
9. 给零基础同学的 3 个小实验 直接用 main_cli.py 做:
实验 1:同一个问题,换 system prompt 1 2 3 4 5 python src/agent/main_cli.py "给我一份一周学习计划" --system "你是温和的老师,说话要鼓励学生" python src/agent/main_cli.py "给我一份一周学习计划" --system "你是严格的教练,要求学生必须执行"
观察点: 同样的问题,不同的人设,回答风格有什么不同?
实验 2:对比流式与非流式 1 2 3 4 5 python src/agent/main_cli.py "解释什么是提示词工程" python src/agent/main_cli.py "解释什么是提示词工程" --no-stream
观察点: 两种方式在用户体验上有什么区别?
实验 3:调 temperature 看风格变化 1 2 3 4 5 python src/agent/main_cli.py "给我3个自我介绍版本" --temperature 0.2 python src/agent/main_cli.py "给我3个自我介绍版本" --temperature 0.9
观察点: 回答的多样性有什么变化?
10. 运行效果示例 以下是实际运行效果:
1 2 3 4 5 6 7 8 $ python src/agent/main.py Assistant: 为你设计一个科学的早起计划,帮助你在不牺牲健康的前提下,逐步养成早起习惯... 早起本身不是目的,获得高质量的早晨时间才是... **切勿突然提前1-2小时起床!** 这会导致睡眠不足...
11. 一句话总结 这个项目本质上是在教你一件事:AI 应用的第一步,不是复杂算法,而是先打通调用链路并理解输入/输出机制。
当你真正理解这条链路后,再去学多轮记忆、RAG、Agent 工具调用,会轻松很多。
12. 建议的下一步学习顺序 1 2 3 4 5 6 7 8 9 10 11 graph LR A[当前阶段<br/>单次调用] --> B[多轮对话<br/>保存历史messages] B --> C[提示词工程<br/>设计稳定的Prompt] C --> D[RAG<br/>引入外部知识库] D --> E[复杂Agent<br/>工具调用与编排] style A fill:#e8f5e9 style B fill:#e3f2fd style C fill:#fff3e0 style D fill:#fce4ec style E fill:#f3e5f5
让程序支持多轮对话(保存历史 messages)
设计更稳定的提示词模板(Prompt Template)
引入外部知识库(RAG)
再考虑更复杂的 Agent 编排
先小步快跑,持续可运行,比一次做”大而全”更重要。
更多信息与项目最新内容请访问:https://git.liupx.com/study/openai_demo_first