☰
Python 8天入门收官:大模型调用脚本综合实战指南
2026/10/24 4:29:29 网站建设 项目流程

第八天了。前面七篇如果你是一路跟下来的,应该已经能写出带循环、函数的 Python 脚本,也知道第三方库怎么装、异常怎么处理、文件怎么读写。但说实话,这些知识点单独拎出来都会,和把它们拼在一起完成一个能用的项目,中间还隔着一段距离。我见过不少人学完基础后一碰到真实需求就发怵,不是因为语法没学会,而是不知道从哪个文件开始写、哪些知识点该用在哪。

这篇 Python 8天极速入门笔记的收官篇,就用一个完整的大模型调用脚本把这些点全部串起来。它会用到变量与数据类型、流程控制、函数与模块、文件操作、异常处理、面向对象、第三方库管理这些你前七天积累的所有东西。你照着做完,收获的不再是一堆孤立的语法知识,而是一个真正能启动、能对话、能记录日志、能保存历史的大模型客户端脚本。这份成果可以直接放进简历项目栏,也可以作为你后面写更复杂工具的基础骨架。

1. 综合实战的整体设计:为什么拿“大模型调用”来收官

1.1 8天闭环的最后一环

我把这套入门笔记设计成 8 天,是因为我觉得对大多数想往大模型工程方向走的初学者来说,学习周期不能太长,太长就容易断;也不能只讲语法,只讲语法等于白学。前七天分别是环境搭建、变量与数据类型、流程控制、函数与模块、文件与异常、面向对象、第三方库与网络请求。到了第八天,必须把这些东西统一投进一个真实场景里验证一遍,否则你不知道自己到底学会了没有。

为什么偏偏选“大模型调用”作为综合实战的题目?因为它是当前工程岗位上最常见的需求之一,同时它的技术密度足够高:你要发 HTTP 请求、要处理鉴权、要解析 JSON、要处理网络超时和服务端限流、要管理多轮对话的上下文、要考虑流式输出带来的体验问题、还要把日志写到文件里方便排查。这些不是孤立的语法题,而是真实工程里每天都会遇到的组合拳。

相比之下,写一个计算器、写一个学生管理系统,虽然也能练语法,但和“大模型工程师”这个目标之间的桥梁不够直接。大模型调用脚本能让你在学习的最后一天就接触到真实岗位的工作方式:配置密钥、调用接口、处理返回、控制成本、优化交互。做完这一篇,你已经具备了独立写一个“能用的小工具”的能力。

1.2 前7天知识点与实战落点的对应关系

综合实战最忌讳的是“看着代码都认识,但不知道为什么要这么写”。所以我在设计脚本时,刻意让前七天的每一个核心知识点都能在代码里找到明确位置。

前7天知识点综合实战中的落点为什么放在这里
变量与基本数据类型配置模块中的 API Key、模型名称、温度参数真实代码中所有可变化的东西都应该集中管理
字符串处理与格式化构造 messages 消息列表、拼接系统提示词大模型接口传输的核心就是一个结构化的字符串列表
条件判断与循环多轮对话主循环、失败重试逻辑决定程序在“正常、异常、边界”三种情况下怎么走
函数与模块拆分成 config、client、conversation、main 四个模块单一职责,方便后续单独扩展和测试
文件读写保存对话历史、写入日志文件不能只在控制台里看结果,要有可追溯的记录
异常处理捕获网络超时、HTTP 错误状态、JSON 解析异常大模型接口在真实网络环境下很不稳定,必须处理
面向对象LLMClient、Conversation 两个核心类把数据和操作封装在一起,复用性更好
第三方库与虚拟环境requests、python-dotenv、argparse 的安装与使用工程不是纯标准库,要学会管理依赖

这张表建议你收藏一下。以后写任何 Python 项目前,都可以先画一张类似的表:先列知识点,再列落点,最后写代码。这样你就不会写出一个“把所有逻辑堆在 main.py 里”的玩具脚本,而是一个结构清晰、别人能看懂、自己两周后还能维护的工程雏形。

2. 动手前先把地基打好

2.1 Python 版本与虚拟环境准备

我默认你前七天已经装好了 Python,但这里还是要强调一个版本问题:请确认你当前用的是 Python 3.10 及以上版本。为什么要 3.10?因为后面的代码里我会用到联合类型写法dict | None,这是 3.10 才正式支持的语法。另外新版第三方库对旧 Python 的支持正在逐步收窄,你用 3.11 或 3.12 会更省心。

项目一定要建在虚拟环境里。命令很简单:

mkdir llm_demo && cd llm_demo python -m venv venv

激活虚拟环境,Windows 下是:

venv\Scripts\activate

macOS / Linux 下是:

source venv/bin/activate

激活后命令行前面会出现(venv)标识,说明你已经进入了项目隔离环境。我见过有人图省事,所有项目都装到全局环境,结果 A 项目要 requests 2.28,B 项目要 requests 2.31,升级一个把另一个搞崩了。虚拟环境就是为了避免这种混乱。

另外有一个实操中的细节:项目路径尽量不要用中文。Windows 下中文路径偶尔会出现编码识别问题,虽然不是必现,但排查起来极其浪费时间。所有文件和目录统一用英文小写加下划线,这是 Python 社区的主流习惯。

2.2 依赖安装与密钥配置

这个实战项目需要的第三方库不多,一个 requests 用来发 HTTP 请求,一个 python-dotenv 用来读取环境变量,argparse 是标准库,不用额外装。把它们写进 requirements.txt:

requests>=2.31.0 python-dotenv>=1.0.0

安装:

pip install -r requirements.txt

大模型服务的 API Key 不要写死在代码里。一旦你的代码被提交到公开仓库,密钥就会泄露,轻则被盗用产生费用,重则影响整个账号安全。正确做法是放在项目根目录的 .env 文件里:

API_KEY=你的密钥 BASE_URL=https://api.example.com/v1 MODEL_NAME=your-model-name

然后在代码里通过 python-dotenv 读取。.env 文件要写进 .gitignore,确保它永远不会被提交。

注意:复制密钥的时候容易把前后的空格或者换行符一起带进去,导致鉴权失败。如果你遇到 401 报错,先检查密钥格式。

3. 完整大模型调用脚本:模块化设计与关键代码

3.1 脚本整体目录结构与模块职责

我不建议把几百行代码全塞进一个 main.py。刚开始你觉得方便,一旦要加功能或者排查问题,就会非常痛苦。我们这个项目按职责拆成四个模块:

llm_demo/ ├── config.py # 读取配置参数 ├── llm_client.py # 封装大模型 API 请求与响应处理 ├── conversation.py # 管理多轮对话上下文 ├── main.py # 交互主程序 ├── requirements.txt └── .env

config.py 只负责配置,llm_client.py 只负责和 API 打交道,conversation.py 只负责数据组织,main.py 只负责把上面三个串起来跑。这就是所谓的“单一职责”。后续你想加日志、加数据库、加 Web 服务,都只需要动其中某一个文件,而不是翻遍整个项目。

3.2 配置模块:把可变参数集中管理

config.py 的核心作用只有一个:让所有可变的配置项集中可见。代码非常简单,但非常重要:

import os from dotenv import load_dotenv load_dotenv() class Config: API_KEY = os.getenv("API_KEY", "") BASE_URL = os.getenv("BASE_URL", "https://api.example.com/v1") MODEL_NAME = os.getenv("MODEL_NAME", "your-model-name") TEMPERATURE = float(os.getenv("TEMPERATURE", "0.7")) MAX_TOKENS = int(os.getenv("MAX_TOKENS", "1024")) TIMEOUT = int(os.getenv("TIMEOUT", "60")) MAX_RETRIES = int(os.getenv("MAX_RETRIES", "3")) RETRY_BACKOFF = int(os.getenv("RETRY_BACKOFF", "2")) LOG_FILE = os.getenv("LOG_FILE", "llm_demo.log")

为什么都用大写常量?因为这是 Python 社区约定俗成的做法,一看就知道是配置项而不是运行中的临时变量。为什么写成一个类而不是直接写API_KEY = os.getenv(...)?因为类给了一个统一的命名空间,你在其他模块里通过config.API_KEY访问,语义清晰,也能 IDE 自动补全。

TEMPERATURE 是采样温度,控制回答的随机性。做代码生成、数学计算这类任务,建议调低到 0.2 左右;做创意写作、头脑风暴,可以调到 0.8 以上。MAX_TOKENS 是单次回复的最大 token 数,注意它限制的是“模型输出”的长度,不是请求的总长度。

TIMEOUT、MAX_RETRIES、RETRY_BACKOFF 这三个参数是给网络请求异常兜底的。大模型 API 不一定每次都能稳定返回,尤其是高峰期,超时和限流都很常见。超时时间不建议设置太短,大模型生成比较长的回答时,耗时可能超过 30 秒。

3.3 客户端封装:请求、重试与流式响应

llm_client.py 是整个脚本的核心。我选择用类来封装,一个 LLMClient 实例负责和 API 服务商的所有通信。这样设计的好处是:main.py 里只需要client = LLMClient(config),之后所有复杂的请求逻辑都被隔离在这个类内部。

import json import time import requests class LLMClient: def __init__(self, config): self.config = config self.session = requests.Session() self.session.headers.update({ "Authorization": f"Bearer {config.API_KEY}", "Content-Type": "application/json" }) def chat(self, messages, stream=False): payload = { "model": self.config.MODEL_NAME, "messages": messages, "temperature": self.config.TEMPERATURE, "max_tokens": self.config.MAX_TOKENS, "stream": stream } url = f"{self.config.BASE_URL}/chat/completions" for attempt in range(self.config.MAX_RETRIES): try: response = self.session.post(url, json=payload, timeout=self.config.TIMEOUT) if response.status_code == 429 or response.status_code >= 500: raise requests.exceptions.RequestException( f"Server returned {response.status_code}, retrying..." ) response.raise_for_status() if stream: return self._parse_stream(response) return self._parse_json(response) except requests.exceptions.RequestException as exc: if attempt == self.config.MAX_RETRIES - 1: raise exc wait_time = self.config.RETRY_BACKOFF ** attempt time.sleep(wait_time) def _parse_json(self, response): data = response.json() return data["choices"][0]["message"]["content"] def _parse_stream(self, response): collected = [] for line in response.iter_lines(decode_unicode=True): if not line or not line.startswith("data: "): continue data_str = line[6:] if data_str == "[DONE]": break data = json.loads(data_str) delta = data["choices"][0].get("delta", {}) if "content" in delta: piece = delta["content"] collected.append(piece) print(piece, end="", flush=True) print() return "".join(collected)

几个关键设计需要解释一下。第一,用requests.Session()而不是直接requests.post(),因为 Session 会自动复用底层 TCP 连接,在连续多轮对话中性能更好。第二,raise_for_status()会在 HTTP 状态码非 2xx 时抛出异常,保证错误不会被静默吞掉。第三,重试逻辑使用指数退避:第一次失败等 2 秒,第二次等 4 秒,减少对服务端的二次冲击。

流式解析那里,line[6:]是把data:前缀去掉,然后按行解析 JSON。很多新手在这里踩坑,以为整个响应就是一个 JSON,直接response.json(),结果解析失败,因为流式响应是“多行 JSON 拼接”而成的。另外要注意响应体里可能包含注释行或空行,所以解析前先做startswith("data: ")判断。

3.4 多轮对话管理:消息列表与滑动窗口

大模型 API 本身是无状态的,它不记得你上一轮说了什么。所谓多轮对话,实际上是你每次请求时把之前所有的消息都带上,由模型根据完整上下文生成回复。所以 conversation.py 的核心任务就是维护一个 messages 列表。

class Conversation: def __init__(self, system_prompt=None): self.messages = [] if system_prompt: self.messages.append({"role": "system", "content": system_prompt}) def add_user_message(self, content): self.messages.append({"role": "user", "content": content}) def add_assistant_message(self, content): self.messages.append({"role": "assistant", "content": content}) def get_messages_for_request(self): # 避免上层直接修改内部列表 return list(self.messages) def estimate_tokens(self): text_length = sum(len(m["content"]) for m in self.messages) return int(text_length * 1.5) # 粗略估算:中文/英文混合场景 def trim_to_window(self, max_tokens=4000): while self.estimate_tokens() > max_tokens and len(self.messages) > 1: # 去掉最早的一条非 system 消息 removed_index = 0 if self.messages[0]["role"] == "system": removed_index = 1 self.messages.pop(removed_index)

每条消息是一个 dict,包含role和content。role只有三种可能:system代表系统设定,user代表用户输入,assistant代表模型回复。为什么需要 system 消息?因为它用来设定模型的身份和行为口径。比如你可以设置“你是一个严谨的 Python 代码审查助手,回答时先给结论再给解释”,这比每次在用户消息里重复强调更稳定。

estimate_tokens 里的text_length * 1.5是一个工程上常用的粗略估算方法。严格来说,不同模型的 tokenizer 不一样,中英文比例也不一样,但你不需要在入口脚本里引入重型 tokenizer 库。够用来做滑动窗口就行。

trim_to_window 解决的是“上下文无限增长”的问题。模型对请求的总 token 数有限制,如果一直聊下去,messages 越来越大,最终会超出限制导致请求失败。滑动窗口的策略是:超过阈值时,优先删掉最早的用户/助手对话,保留 system 提示词。这是一张安全网,保证程序不会聊着聊着突然崩溃。

3.5 主程序:把一切串起来的交互入口

main.py 使用 argparse 解析命令行参数,然后进入循环。交互命令我设计了三个:/quit退出,/new清空对话,/save将当前对话保存到 JSON 文件。这些命令本身也是对你前七天文件操作能力的检验。

import argparse import json import logging from datetime import datetime from config import Config from conversation import Conversation from llm_client import LLMClient def setup_logger(config): logger = logging.getLogger("llm_demo") logger.setLevel(logging.INFO) formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") file_handler = logging.FileHandler(config.LOG_FILE, encoding="utf-8") file_handler.setFormatter(formatter) console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) logger.addHandler(file_handler) logger.addHandler(console_handler) return logger def save_conversation(conversation): timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"conversation_{timestamp}.json" with open(filename, "w", encoding="utf-8") as f: json.dump(conversation.messages, f, ensure_ascii=False, indent=2) return filename def main(): parser = argparse.ArgumentParser(description="大模型调用完整脚本") parser.add_argument("--system", default="你是一个乐于助人的 AI 助手。", help="system 提示词") parser.add_argument("--stream", action="store_true", help="启用流式输出") args = parser.parse_args() config = Config() logger = setup_logger(config) client = LLMClient(config) conversation = Conversation(system_prompt=args.system) print("大模型交互脚本已启动。输入 /quit 退出,/new 开启新对话,/save 保存记录。") logger.info("对话启动,模型:%s,流式:%s", config.MODEL_NAME, args.stream) while True: user_input = input("你: ").strip() if not user_input: continue if user_input == "/quit": logger.info("用户退出对话") break elif user_input == "/new": conversation = Conversation(system_prompt=args.system) print("已开启新对话。") logger.info("对话已重置") continue elif user_input == "/save": filename = save_conversation(conversation) print(f"对话已保存到 {filename}") logger.info("对话保存到 %s", filename) continue conversation.add_user_message(user_input) try: reply = client.chat(conversation.get_messages_for_request(), stream=args.stream) conversation.add_assistant_message(reply) logger.info("用户输入:%s | 回复长度:%d", user_input[:50], len(reply)) except Exception as exc: print(f"请求失败:{exc}") logger.error("请求异常:%s", exc, exc_info=True) conversation.messages.pop() # 删除刚才添加的 user 消息 if __name__ == "__main__": main()

代码逻辑不复杂,但有几个细节值得注意。conversation.messages.pop()这行处理的是“请求失败时回滚”:因为用户消息已经加进列表,如果不回滚,下一轮请求会把失败的那条消息也带上,造成上下文污染。logger.info里对用户输入做了[:50]截断,避免日志文件被超长输入撑爆。

启动方式:

python main.py --stream

加上--stream参数后,模型的回复会像 ChatGPT 那样一个字一个字地蹦出来,体验比干等十几秒然后一次性输出好很多。流式在工程上还意味着:你可以更早地开始处理部分结果,比如实现“边生成边翻译”的效果。

3.6 日志与异常处理:让程序“自解释”

之所以单独把日志拎出来讲,是因为我见过太多脚本没有任何日志输出。程序跑挂了,一屏报错,只能靠记忆排查。真实工程里,日志是你的第一排查工具。上面 setup_logger 里我同时加了 FileHandler 和 StreamHandler,一个写文件,一个打控制台。文件用 UTF-8 编码,保证中文不会被写成乱码。

异常处理这里采用的是“外层兜底 + 内层精细化”的策略。请求细节中的超时、限流、HTTP 状态码问题在 LLMClient 内部处理;而所有可能意想不到的异常,在主程序的 try-except 里被捕获,记录到日志,并给用户一个友好提示。

有人觉得这样会“吞掉错误”。其实不会,logger.error("...", exc_info=True)会把完整堆栈写进日志文件,你随时可以翻出来看。控制台只展示一句话“请求失败”,是给普通用户看的;详细堆栈是给开发者看的,两不耽误。

4. 运行效果与踩坑实况

4.1 第一次运行前的自检清单

在你运行python main.py --stream之前,我建议花两分钟过一遍下面这份清单。别嫌麻烦,这几个问题几乎覆盖了新手第一次运行失败的 80% 原因。

  • 虚拟环境是否激活?命令行前面有没有(venv)标识?没有的话,当前用的是全局 Python,很可能找不到 requests。
  • .env 文件是否存在?里面 API_KEY 是否填了真实值?
  • API_KEY 有没有多余的空格或换行?从网页复制密钥时很容易带进去。
  • 模型名称是否和你在服务商后台看到的一致?模型名写错通常会报 404 或者类似“model not found”的错误。
  • 网络连通性是否正常?如果网络环境本身不稳定,任何调用都会超时。

我遇到过最典型的例子是:某同学 .env 里写的API_KEY = sk-xxx,变量名拼写错了,写成APY_KEY,然后花了一个小时看代码,最后发现是读取到的值是空的。所以我后来养成了一个习惯:写代码第一件事,先print(config.API_KEY[:4])看看密钥有没有读进来,而不是直接闷头调接口。

4.2 高频报错排查速查表

把项目发布到社区后,我收到过不少反馈,整理下来高频报错就集中在下面几个。

报错信息可能原因解决方案
401 / AuthenticationErrorAPI Key 无效、格式错误、权限不足检查密钥是否完整,确认账号是否开通模型访问权限
404 / ModelNotFound模型名称拼写错误,或当前账号无权访问该模型登录服务商后台核对模型名,选用有权限的模型
429 / RateLimitExceeded请求频率超过限额,或余额不足降低请求频率,检查账号余额,等待后重试
500 / 502服务端临时故障启用重试机制,指数退避等待
requests.exceptions.Timeout请求超过设置的 TIMEOUT调大超时时间,检查网络稳定性
JSONDecodeError把流式响应当普通 JSON 解析确认stream=True时走_parse_stream分支
UnicodeEncodeErrorWindows 控制台编码不支持中文运行前设置set PYTHONIOENCODING=utf-8

这里我想重点说下 429。很多人一看到 429 就以为是自己的代码写错了,其实这是正常的限流机制在起作用。大模型服务商为了防止单个用户占满资源,会按账号或 IP 限制请求频率。处理方式不是修改代码,而是“退避重试”。我们脚本里的指数退避就是干这个的:第一次等 2 秒,第二次等 4 秒,第三次等 8 秒。如果 3 次之后还是 429,说明当前确实超过了限额,该歇一会儿了。

4.3 上下文无限增长的坑

我自己刚写第一个大模型脚本的时候,天真地以为只要一直往 messages 里 append 就行。结果聊到第 30 轮左右,请求报错,提示总 token 数超出模型上限。这就是因为我没有做任何上下文管理。

你需要理解一个概念:模型的上下文窗口是有限的。假设某模型支持 32000 token 的上下文,你的多轮对话里每轮平均消耗 800 token,那么最多 40 轮就会撑爆。一旦超出,请求会直接失败,而且通常不是报“内容太长”这种友好错误,而是 400 Bad Request 加一串难懂的参数提示。

解决方案我在 Conversation 类里已经写了滑动窗口:超过 token 阈值就丢最早的消息。但滑动窗口有个副作用:丢掉的早期信息模型再也看不到了。如果用户在第 20 轮问“我刚才第一轮说的需求还记得吗”,模型会一脸茫然。

更高级的做法是“摘要记忆”:每累计一定轮次,就调用一次模型,把当前 messages 里较早的对话压缩成一段摘要,然后把摘要替换掉原始消息。这样做既保住了长程信息,又控制住了 token 用量。等你自己把基础脚本跑通之后,可以按照这个思路继续优化。

4.4 成本控制与效率调优心得

调大模型接口不是免费的,按 token 计费。我实测下来,一个普通问答场景,用户输入 50 字加模型回复 300 字,差不多消耗 400 到 600 token。如果带 system 提示词和历史对话,每轮会更多。看似不多,但如果你写了个批量脚本,一次性处理 1000 条数据,成本就会变得非常可观。

控制成本的第一招是设置 MAX_TOKENS。很多新手把 MAX_TOKENS 设成 4096,但实际模型回 300 字就够了。过大的上限就像开着水龙头不关,既浪费钱又拖慢速度。第二招是控制发送给模型的历史消息条数。我们脚本里的滑动窗口就是干这个的,你甚至可以把阈值调得激进一些,比如“只保留最近 3000 token”,对多数短对话场景完全够用。第三招是善用缓存。如果用户连续问同一个问题,直接返回上一次的结果,不需要重复调用 API。在 production 里,可以加一层简单的磁盘缓存或者 Redis 缓存。

效率方面,我强烈建议你默认开启--stream流式输出。从用户感知时间来看,流式模式下第一屏内容可能 1 秒内就出来了,而不流式要等全部生成完毕,动辄十几秒。如果你做的是一个面向外部用户的产品,流式输出带来的体验提升是质的飞跃。

5. 从“能跑”到“好用”:脚本的进一步扩展

5.1 用 argparse 加更多交互参数

目前的 main.py 已经支持--system和--stream两个参数。你还可以继续扩充,比如:

python main.py --model your-model-name --max-tokens 2048 --save-log

argparse 的好处是你不需要在代码里硬编码任何运行参数。发布给同事用时,别人能通过--help快速了解如何使用:

python main.py --help

这个习惯非常重要。一个只能改代码才能调参的脚本,和一个能通过命令行调参的脚本,专业度差距非常大。argparse 是标准库,零成本,建议你所有脚本都加上。

5.2 结构化输出与函数调用

基础脚本只能拿到模型的纯文本回复。但如果你希望模型返回 JSON 结构,比如“从用户问题中提取三个关键词”,该怎么办?做法是在 system 提示词里明确要求:“请严格输出 JSON 格式,包含 keywords 数组。”然后在代码里用json.loads()解析。

更进一步,你还可以接入函数调用机制。所谓函数调用,就是让模型决定“应该调用哪个本地函数”,模型返回一个包含函数名和参数的结构体,然后你在本地执行它。典型场景:用户说“帮我查一下北京的天气”,模型返回get_weather(city="北京"),你的代码调用真实的天气接口,再把结果交给模型组织成自然语言回复。

这其实是大模型应用从“聊天机器人”走向“智能体”的关键一步。当前脚本里的 LLMClient 只需要增加一个 tools 参数,就能支持这种调用方式。建议你把基础脚本跑熟之后,从这个方向入手深入研究。

5.3 批量文档摘要

你想把这 8 天的笔记做成摘要,或者给一批技术文档生成简介,总共有 50 个文件。逐个复制粘贴到对话框里操作太蠢了,正确姿势是写一个批量脚本。

思路也很简单:用glob.glob("*.md")拿到所有文件路径,逐个读取内容,调用模型的 chat 接口,把“请对以下文档生成 200 字摘要:\n\n" + content 作为用户消息发送,然后把结果写入summary_文件名.md。批量跑的时候要注意三点:一是加time.sleep(1)控制请求频率,避免被限流;二是定期保存进度,防止跑到一半程序崩了全部重来;三是用tqdm显示进度条,方便观察整体状况。

这个脚本做出来后,和咱们这篇的综合实战脚本共享同一个 LLMClient 类,你只需要from llm_client import LLMClient就能直接复用。这就是当初拆分模块的好处。

5.4 用 FastAPI 把它包装成 HTTP 服务

如果你想把脚本能力暴露给前端,或者给其他系统通过 HTTP 调用,可以用 FastAPI 包一层。核心代码非常精简:

from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class ChatRequest(BaseModel): prompt: str system: str = "你是乐于助人的 AI 助手" @app.post("/chat") def chat(req: ChatRequest): config = Config() client = LLMClient(config) conv = Conversation(system_prompt=req.system) conv.add_user_message(req.prompt) reply = client.chat(conv.get_messages_for_request()) return {"reply": reply}

这样启动后,任意语言写的前端都能通过 POST/chat接口来对话。body 里传 JSON,返回 JSON,语义非常干净。如果你想支持流式输出,FastAPI 里可以用StreamingResponse,把 LLMClient 流式解析的生成器直接返回给浏览器。

5.5 保存对话记录到本地文件库

当你把脚本部署起来之后,用户每轮对话都会产生数据,这些数据是很有价值的资产。最简单的处理方式是把对话以 JSON 格式落盘,复杂一点可以接入 SQLite 甚至向量数据库。我建议你先用 JSON 文件存储,实现一次之后再考虑数据库方案。原因很简单:JSON 可视化程度高、不需要额外服务、适合个人项目;数据库虽然查询能力强,但引入的复杂度对你的项目收益不大。

一个稳定的文件命名方案是“用户ID_会话ID_时间戳.json”,内容结构就是 conversation.messages 那个列表。等你将来要做用户行为分析、模型效果评估时,这些历史数据就是你最宝贵的基础。

6. 写在最后:8天之后,怎么把成果变成长期生产力

这 8 天如果你完整跟下来,最大的收获不是“我会写 Python”这个结论,而是“我能把一个需求翻译成 Python 脚本”的底气。前者是知识,后者是能力,两者之间的桥梁就是动手做综合实战。

关于这个大模型调用脚本,我最后分享三个实际使用中的建议。第一个建议:不要急着上复杂框架,先把这个小脚本用熟。我见过很多初学者一上来就研究各种重型框架,结果连最基础的 messages 结构都没弄明白,走都走不稳就想跑,后面大概率会摔得很惨。第二个建议:把你自己的高频任务沉淀成命令。我现在写代码遇到不懂的问题,会直接在这个脚本里问模型“为什么这段代码报错”,把它当成自己的编程助手。第三个建议:脚本里的代码不要背,要“抄”出自己的风格。你先把模块结构抄懂,再根据自己的需求改功能,改着改着就成了你自己的东西。

我印象最深的一次经历,是某天凌晨部署环境出问题,现场没有有效的图形界面,只有一个命令行终端。我拿起手机打开自己 8 天前写的这个脚本,改了几行代码,通过 API 让模型帮忙分析报错日志,最终定位到了问题。那一次我真正意识到:学完 8 天之后不是终点,而是你开始用 Python 去解决真实问题的起点。现在,打开终端,运行这篇笔记里的脚本,输入你的第一个问题,看看模型怎么回答你。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询