入门python

2. Python 基础语法 - 面向前端工程师

Python 是一个简洁而强大的编程语言,因其易于使用而非常适合初学者,也是人工智能学习的必备技能。本章节将从前端工程师的角度,结合 JavaScript(以下简称为 JS)的语法习惯,讲解 Python 的基本语法和常用核心功能。


2.1 基础数据类型 & 运算符
2.1.1 Python 数据类型(对比 JS)

Python 和 JS 的基础数据类型有很多相似之处,但 Python 的弱类型特性和强大的内置方法使它更加灵活。从前端工程师的角度来看,这些数据类型是你必须掌握的核心:

  • int(整数)

    • Python: int 表示整数类型,可以执行任意大的数值计算,写法极其简单。

      a = 42      # Python 的 int
      
    • JS: 类似 Number 类型。

      let a = 42; // JS 的 Number
      
  • float(浮点数)

    • Python: float 表示小数或带有小数点的数字。

      b = 3.14    # PI 值
      
    • JS: 依然使用 Number

      let b = 3.14;
      
  • str(字符串)

    • Python: 用单引号 ' 或双引号 " 包裹字符串。支持多行字符串(用三引号 '''""")。

      name = "Alice"
      multiline_text = '''这是一段
      多行字符串。'''
      
    • JS: 也支持字符串,但没有 ''' 多行写法,使用模板字符串代替。

      let name = "Alice";
      let multiline_text = `这是一段
      多行字符串。`;
      
  • bool(布尔型)

    • Python: 布尔值为 TrueFalse(注意首字母大写)。

      is_active = True
      
    • JS: 布尔值为 truefalse(全小写)。

      let is_active = true;
      
2.1.2 Python 的动态类型与类型检查

Python 是动态类型语言,不需要预定义变量类型;它可以随时更改为其他类型。例如:

x = 10   # x 是 int
x = "Hello"  # 现在 x 是 str

JS 也是动态类型语言,但可以通过 TypeScript 提供静态类型检查。而 Python 提供类似的 Type Hint 静态检查(详见 2.3.2)。

2.1.3 运算符和逻辑
  • 算术运算符+, -, *, /(除,结果总是浮点数), //(整除), %(取余), **(幂运算)。

    print(5 / 2)  # 结果 2.5
    print(5 // 2) # 整除,结果 2
    print(2 ** 3) # 幂运算,2^3 = 8
    
  • 逻辑运算符:Python 与 JS 略有不同。

    • Python 使用 and, or, not 替代 JS 中的 &&, ||, !

      True and False  # False
      True or False   # True
      not True        # False
      
  • 对比 =====

    • Python 的 == 是值比较(类似于 JS 的 ==),但 Python 没有严格相等比较符。

      1 == 1.0  # True
      1 is 1.0  # False,is 用于比较对象标识(引用地址)是否相同
      

      而在 JS 中:

      1 == '1';  // true
      1 === '1'; // false
      

2.2 常用集合 & 对应操作
2.2.1 列表(List)

Python 的列表类似 JS 的数组,但功能更加丰富。

# Python 列表
colors = ["red", "green", "blue"]
colors.append("yellow")   # 添加元素
colors.pop()              # 删除最后一个元素
colors.sort()             # 按字典序排序
subcolors = colors[1:3]   # 切片,获取索引 1 到 2 的子序列

在 JS 中类似:

// JS 数组
let colors = ["red", "green", "blue"];
colors.push("yellow");   // 添加元素
colors.pop();            // 删除最后一个元素
colors.sort();           // 按字典序排序
let subcolors = colors.slice(1, 3);  // 获取子数组
2.2.2 字典(Dict)

Python 的字典类似于 JS 的对象键值对,但有更多功能。

# Python 字典
person = {"name": "Alice", "age": 25}
person["city"] = "Beijing"  # 新增键值对
del person["age"]           # 删除键值对

在 JS 中类似于:

// JS 对象
let person = { name: "Alice", age: 25 };
person.city = "Beijing";   // 新增键
delete person.age;         // 删除键
2.2.3 元组(Tuple)与集合(Set)
  • 元组 是不可变的列表,例如 (1, 2, 3);它更类似于不可变的数组。
  • 集合 是没有重复值的元素集,例如 {1, 2, 3}。JS 中用 Set 表示。
# Python 元组和集合
coordinates = (1, 2)  # 元组
unique_values = {1, 2, 3}  # 集合

类似于 JS 中的 Set:

const uniqueValues = new Set([1, 2, 3]);
2.2.4 解构赋值 & 列表推导式
# 解构赋值
a, b = [1, 2]

# 列表推导式
squared = [x**2 for x in range(5)]
print(squared)  # [0, 1, 4, 9, 16]

在 JS 中:

// 解构赋值
let [a, b] = [1, 2];

// 类似数组映射
let squared = Array.from({ length: 5 }, (_, i) => i ** 2);
console.log(squared); // [0, 1, 4, 9, 16]

2.3 函数与类型提示
2.3.1 定义和调用函数
def add(a, b):
    return a + b

result = add(2, 3)  # 调用函数

在 JS 中:

function add(a, b) {
    return a + b;
}
let result = add(2, 3);
2.3.2 Type Hints 类型提示

Python 的类型提示类似于 TypeScript,通过在函数参数和返回值中指定类型来进行静态检查。

def greet(name: str) -> str:
    return f"Hello, {name}"

配合工具(如 mypy),可检测类型错误。

2.3.3 Lambda 表达式
# Python
square = lambda x: x ** 2
print(square(4))  # 输出 16

JS 中的箭头函数类似:

// JS
const square = x => x ** 2;
console.log(square(4)); // 输出 16

2.4 异步编程简介

Python 的异步部分借鉴了 JS 的 Promise 和 async/await 模式,但在实现机制上有所不同。

import asyncio

async def fetch_data():
    print("Fetching...")
    await asyncio.sleep(2)  # 模拟 I/O 操作
    return "Data fetched"

async def main():
    result = await fetch_data()
    print(result)

# 运行异步代码
asyncio.run(main())

JS 中类似:

async function fetchData() {
    console.log("Fetching...");
    await new Promise(resolve => setTimeout(resolve, 2000)); // 模拟 I/O 操作
    return "Data fetched";
}

async function main() {
    const result = await fetchData();
    console.log(result);
}
main();

通过以上介绍,前端工程师可以快速上手 Python,进而为后续的 AI 学习打下基础。Python 的语法既简单又直观,适合快速实现复杂的人工智能算法和数据运算任务。

作为习惯了 JavaScript 生态的前端工程师,刚刚进入 Python 的世界时,最容易感到“水土不服”的地方往往不是语法,而是代码组织方式包管理机制

在 Node.js 中,你习惯了 node_modules 黑洞,习惯了 import/export,习惯了 package.json 一把梭。转到 Python,你会发现它既相似又不同。别担心,这篇教程将用你最熟悉的“前端语言”来翻译 Python 的模块化世界,助你无缝迁移。


3. 模块 & 包管理

3.1 使用模块:从 CommonJS/ESM 到 Python Import

在前端,我们经历了从 AMD/CMD 到 CommonJS,再到如今一统天下的 ES Modules (ESM)。Python 的模块系统相对稳定,但在引入机制上与 JS 有微妙的区别。

1. Python 模块 vs Node.js 模块

你可以把 Python 的一个 .py 文件看作一个 Module,把一个包含 __init__.py 的文件夹看作一个 Package(类似于 npm 包)。

功能场景 Node.js (ESM/TS) Python 核心差异点
导入默认 import a from './a' import a Python 导入的是模块对象本身,调用需用 a.func()
导入部分 import { func } from './a' from a import func 语法结构非常像,但 Python 不需要花括号。
重命名 import { a as b } from './mod' from mod import a as b 逻辑完全一致,关键词不同。
导入所有 import * as tools from './mod' from mod import * 警告:Python 极不推荐 import *,因为会污染命名空间,且不知道变量来源。

代码对比实战:

Node.js 写法:

// utils.js
export const add = (a, b) => a + b;

// main.js
import { add } from './utils';
console.log(add(1, 2));

Python 写法:

# utils.py
def add(a, b):
return a + b

# main.py
from utils import add
print(add(1, 2))

2. 标准库:Python 的 “Batteries Included” 哲学

Node.js 的内置库很精简(fs, path, http),很多功能需要求助于 npm。而 Python 号称“自带电池”,标准库极其丰富。作为 AI 工程师,以下四个库是你必须掌握的“瑞士军刀”:

  • os & pathlib (对应 Node 的 fs + path)

    • 做 AI 数据处理时,你需频繁遍历文件夹读取图片或文本。不要再手拼路径了!
    import os
    from pathlib import Path
    
    # 获取当前文件路径 (类似于 __dirname)
    current_dir = os.path.dirname(os.path.abspath(__file__))
    
    # 更好的方式:使用 Pathlib (面向对象风格,强烈推荐)
    base_dir = Path(__file__).resolve().parent
    data_path = base_dir / "data" / "dataset.csv"  # 像拼接字符串一样拼接路径,且跨平台兼容
    
    print(f"数据路径: {data_path}")
    
  • time & datetime (对应 Node 的 Date + performance.now)

    • 在 AI 模型训练中,计算耗时是家常便饭。
    import time
    from datetime import datetime
    
    start_time = time.time()  # 获取时间戳
    
    # 模拟一个耗时操作
    time.sleep(1.5) 
    
    end_time = time.time()
    print(f"训练耗时: {end_time - start_time:.2f} 秒")
    print(f"当前时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    
  • math (对应 JS 的 Math)

    • 虽然做 AI 我们主要用 numpytorch,但基础数学计算仍离不开它。
    import math
    
    # 计算 Sigmoid 函数的某一部分: 1 / (1 + e^-x)
    x = 2
    sigmoid_val = 1 / (1 + math.exp(-x))
    print(f"Sigmoid({x}) = {sigmoid_val}")
    
  • logging (对应 console.log 的进化版)

    • 金牌讲师敲黑板:做后端和 AI 开发,请戒掉到处 print() 的习惯print 是同步阻塞的 stdout 输出,且无法分级。logging 可以帮你记录日志到文件、控制输出级别(Debug/Info/Error),这在长时间的模型训练任务中至关重要。
    import logging
    
    # 配置日志格式
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s'
    )
    
    logging.info("开始加载模型...")
    try:
        # 模拟错误
        x = 1 / 0
    except Exception as e:
        logging.error(f"模型训练崩溃: {e}")
    

3.2 第三方库与依赖管理

前端有 npm/yarn/pnpm,Python 的生态也有对应的工具。

1. pip install:Python 界的 npm

pip 是 Python 的包安装器。它默认从 PyPI (Python Package Index) 下载包,类似于 npm registry。

  • 安装包:pip install requests (等同 npm install axios)
  • 卸载包:pip uninstall requests
  • 查看已安装:pip list

⚠️ 重要区别: Node.js 默认将包安装在项目目录下的 node_modules。Python pip 默认将包安装在全局环境当前激活的虚拟环境中。这就是为什么 Python 开发必须使用虚拟环境(Virtual Environment)的原因,否则不同项目的依赖版本会打架。

2. 依赖管理进阶:requirements.txt vs Poetry

这里是前端同学最容易感到困惑的地方,我们来做个映射:

  • 方案 A:传统派 (requirements.txt)

    这是 Python 的老传统。它只是一个简单的文本文件,列出了依赖包及其版本。

    • 生成依赖文件pip freeze > requirements.txt
    • 安装依赖文件pip install -r requirements.txt

    缺点:它没有区分 dependenciesdevDependencies,也没有锁文件(Lock file)的概念,容易导致“在我机器上能跑,在你机器上跑不起来”。

  • 方案 B:现代派 (Poetry) —— 强烈推荐给前端开发者 🌟

    如果你喜欢 package.json 的管理方式,你一定会爱上 Poetry。它集成了虚拟环境管理、依赖解析、构建发布于一身。

    前端概念 package.json / npm Poetry / pyproject.toml
    初始化 npm init poetry init
    安装依赖 npm install axios poetry add requests
    开发依赖 npm install jest -D poetry add pytest --group dev
    运行脚本 npm run start poetry run python main.py
    锁文件 package-lock.json poetry.lock
    配置文件 package.json pyproject.toml

    Poetry 使用示例:

    # 1. 初始化项目
    poetry init 
    
    # 2. 添加 AI 常用库
    poetry add numpy pandas openai
    
    # 3. 进入虚拟环境 Shell
    poetry shell
    

3.3 组织代码的最佳实践

当你开始写复杂的 AI Agent 或后端服务时,把所有代码塞进 app.py 是绝对不行的。作为前端,你可能熟悉 Vue/React 的组件化目录,或者 NestJS 的模块化架构。我们可以把 NestJS 的优秀思想带入 Python。

1. __init__.py 的魔法

在 Python 目录中放入一个 __init__.py 文件(哪怕是空的),Python 就会将该目录视为一个 Package

  • 它类似于 JS 文件夹里的 index.js
  • 你可以在里面通过 from .module import Service 暴露对外的接口,简化导入路径。

2. 仿 NestJS 架构组织 Python AI 项目

假设我们要开发一个 “AI 文章摘要生成器” 的后端服务,我们可以模仿 NestJS 的 Controller-Service 分层架构来组织 FastAPI 或 Flask 项目:

my-ai-project/
├── pyproject.toml       # 类似于 package.json
├── poetry.lock          # 类似于 package-lock.json
├── README.md
├── .env                 # 环境变量 (API Keys)
└── src/                 # 源码目录
    ├── __init__.py
    ├── main.py          # 入口文件 (类似于 index.ts)
    ├── common/          # 公共模块
    │   ├── utils.py
    │   └── logger.py
    └── modules/         # 业务模块 (类似于 NestJS modules)
        ├── summary/
        │   ├── __init__.py      # 暴露 Service 给外部
        │   ├── controller.py    # 处理 HTTP 请求 (Router)
        │   ├── service.py       # 核心业务逻辑 (调用 OpenAI/LLM)
        │   └── dtos.py          # 数据传输对象 (类似于 Interface/Type)
        └── user/
            ├── ...

代码组织示例 (Service 层):

# src/modules/summary/service.py

# 这是一个纯粹的业务逻辑类,类似于 NestJS 的 Provider
class SummaryService:
    def __init__(self, api_key: str):
        self.api_key = api_key

    def generate(self, text: str) -> str:
        # 这里模拟调用 AI 模型的逻辑
        if not text:
            raise ValueError("文本不能为空")
        return f"这是对文本的摘要处理结果... (长度: {len(text)})"

# src/modules/summary/__init__.py
from .service import SummaryService
from .controller import router 

# 这样外部只需要 from modules.summary import SummaryService 即可

总结:
不要因为 Python 语法简单就写出“面条代码”。利用好 poetry 管理依赖,利用好目录结构分层,你将发现,优秀的工程化思维在任何语言中都是通用的

如果说前面的模块管理是搭建房子的脚手架,那么面向对象编程 (OOP) 就是我们构建摩天大楼的蓝图和砖块。在 AI 开发中,你会发现几乎所有的主流框架(PyTorch, TensorFlow, LangChain)都是基于类(Class)构建的。

别担心,作为熟悉 ES6+ 的前端工程师,你其实已经掌握了 80% 的概念。我们今天的任务,就是把那剩下的 20% 差异补齐,并学会 Python 特有的“语法糖”。


4. 面向对象编程(OOP)

4.1 基本概念 & JS 对比

在 ES6 之前,JavaScript 是通过原型链(Prototype)模拟类的,而 Python 从诞生之初就是基于类的语言。虽然现在两者看起来很像,但“底层味道”完全不同。

1. 类与对象:语法对对碰

我们将创建一个简单的 AI 模型类来对比两者。

JavaScript (ES6+) 写法:

class Model {
// 构造函数
constructor(name) {
 this.name = name;
}

// 方法
predict(inputData) {
 return `${this.name} 正在预测: ${inputData}`;
}
}

// 实例化 (必须用 new)
const myModel = new Model("GPT-4");
console.log(myModel.predict("Hello"));

Python 写法:

class Model:
 # 构造函数 (__init__)
 def __init__(self, name):
     self.name = name

 # 方法 (注意第一个参数必须是 self)
 def predict(self, input_data):
     # f-string 类似于 JS 的模板字符串 `${}`
     return f"{self.name} 正在预测: {input_data}"

# 实例化 (不需要 new 关键字!)
my_model = Model("GPT-4") 
print(my_model.predict("Hello"))

核心差异点:

  1. 没有 new:Python 实例化类就像调用函数一样简单,少敲三个键,效率 Up。
  2. this 变成了 self:这是前端同学最容易报错的地方,我们在下一节细说。
  3. 命名规范:类名依然是大驼峰(PascalCase),但方法名和变量名 Python 强制推荐蛇形命名(snake_case),而不是 JS 的小驼峰(camelCase)。

4.2 self 的用法及初始化器 __init__

这是 Python OOP 中最大的“坑”,也是最大的“显式特性”。

1. __init__ 是什么?
它完全等同于 JS 中的 constructor。当你调用 Model() 时,Python 会自动执行 __init__ 方法。我们通常在这里初始化实例属性。

2. 为什么每个方法都要写 self
在 JS 中,this 是一个隐式上下文,它的指向非常灵活(有时也很让人抓狂,比如在回调函数中丢失 this)。

在 Python 中,显式优于隐式

  • Python 规定:类方法的第一个参数,永远代表实例本身。习惯上我们将它命名为 self(虽然你叫它 me 也可以,但会被同事打)。
  • 当你调用 my_model.predict("data") 时,Python 解释器在后台其实是这样执行的:
    Model.predict(my_model, "data")
    看,my_model 自动作为第一个参数传给了 self

❌ 常见错误示范(前端同学请注意):

class Robot:
    def __init__(self, name):
        self.name = name
    
    # 错误!忘记加 self
    def say_hello(): 
        print("Hello")

r = Robot("Wall-E")
r.say_hello() 
# 报错: TypeError: Robot.say_hello() takes 0 positional arguments but 1 was given
# 原因:你调用时系统自动传了 self 进去,但你的函数定义里没坑位接它。

4.3 类的继承和多态性

在 AI 开发中,我们经常需要继承基础模型类并修改其行为。

1. 继承 (Inheritance)

假设我们要基于通用的 Model 创建一个 LLMModel

# 父类
class Model:
    def __init__(self, name):
        self.name = name
        
    def train(self):
        print(f"{self.name} 开始基础训练...")

# 子类 (继承语法直接写在括号里)
class LLMModel(Model):
    def __init__(self, name, context_window):
        # 调用父类构造函数,类似 JS 的 super(name)
        super().__init__(name)
        self.context_window = context_window

    # 重写 (Override) 父类方法
    def train(self):
        # 可以在重写中调用父类逻辑
        super().train()
        print(f"--> 正在进行大规模语言预训练,上下文窗口: {self.context_window}")

gpt = LLMModel("GPT-Mini", 128000)
gpt.train()

2. 多态 (Polymorphism) 与鸭子类型

Python 是动态强类型语言,它推崇“鸭子类型”(Duck Typing):如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。

这意味着你不需要像 Java 或 TypeScript 那样定义严格的 Interface。只要不同类的对象都有 train() 方法,它们就可以被同等地对待。

3. 多继承 (Multiple Inheritance) —— JS 做不到的事

JS 的类只能 extends 一个父类。但 Python 支持多继承
场景:你有一个 Transformer 架构类,又有一个 VisionModule 视觉模块类,你想造一个“多模态模型”,你可以同时继承这两个类。

class Flyer:
    def fly(self): print("I can fly")

class Swimmer:
    def swim(self): print("I can swim")

# 同时继承两个爹
class Seaplane(Flyer, Swimmer):
    pass

plane = Seaplane()
plane.fly()
plane.swim()

金牌讲师提醒:多继承虽然强大,但容易导致“菱形继承”问题(代码逻辑混乱)。在 AI 实战中,通常使用 Mixin(混入)模式 也就是多继承的一种应用,来给主类添加额外功能。


4.4 dataclass 数据类:轻量对象设计

这是 Python 送给全栈工程师最好的礼物。 🎁

在 AI 和数据处理中,我们经常需要定义一堆只用来“存数据”的对象(类似于 TypeScript 的 interfacetype 定义的数据结构)。

如果用普通类写,太啰嗦了:

# 传统的笨重写法
class TrainingConfig:
    def __init__(self, batch_size, learning_rate, epoch):
        self.batch_size = batch_size
        self.learning_rate = learning_rate
        self.epoch = epoch
    
    def __repr__(self):
        return f"Config(lr={self.learning_rate}, ...)" # 还要自己写打印格式,烦死

使用 @dataclass (Python 3.7+):

这就好比你写了 TypeScript 的 Interface,Python 自动帮你把 Constructor、Print 格式、对比函数全部生成好了!

from dataclasses import dataclass

# @dataclass 是一个装饰器(类似于 TS 的 Decorator)
@dataclass
class TrainingConfig:
    batch_size: int = 32          # 支持类型提示和默认值
    learning_rate: float = 0.001
    epoch: int = 100
    optimizer: str = "Adam"

# 使用
conf = TrainingConfig(batch_size=64, epoch=200)

# 1. 自动生成了漂亮的打印格式 (__repr__)
print(conf) 
# 输出: TrainingConfig(batch_size=64, learning_rate=0.001, epoch=200, optimizer='Adam')

# 2. 像对象一样访问
print(conf.learning_rate)

# 3. 甚至可以直接对比数据内容 (__eq__)
conf2 = TrainingConfig(batch_size=64, epoch=200)
print(conf == conf2) # True (普通类对比的是内存地址,dataclass 对比的是值)

对于前端同学的理解映射:

  • 普通 Class ≈ 包含业务逻辑的 Service / Component。
  • Dataclass ≈ DTO (Data Transfer Object) / TypeScript Interface / Redux State。

学会 dataclass,你在处理 AI 模型繁杂的配置参数(Hyperparameters)时,代码会变得极其优雅、清晰。

上一章我们搭建了面向对象的骨架,现在我们要往里面填充“血肉”了。在 AI 开发中,你 90% 的时间其实不是在写模型架构,而是在处理数据:读取数据集文件、解析 JSON 配置、处理日志时间戳。

这部分内容虽然基础,但 Python 的处理方式极其优雅,尤其是“上下文管理器”这个概念,绝对会让你眼前一亮。


5. Python 中的常见工具

5.1 文件读写与数据处理

在 Node.js 中,我们要么使用 fs.readFile 的回调地狱,要么使用 fs/promises 配合 async/await。Python 的文件操作则显得更加同步和“线性”,同时通过一种特殊的语法结构保证了安全性。

1. 文件操作:告别 fs.close() 的烦恼——上下文管理器 (with)

在操作文件时,最怕的就是打开了文件却忘记关闭(Memory Leak)。Node.js 的垃圾回收机制虽然强大,但文件描述符泄露依然是个问题。

Python 引入了 with 关键字(Context Manager),它保证了无论代码是否报错,文件都会在退出缩进块时自动关闭。这有点像 React 的 useEffect 清理函数,或者 try-finally 的语法糖,但更简洁。

对比实战:读取文本文件

Node.js (Async) 写法:

const fs = require('fs').promises;

async function readData() {
try {
 const data = await fs.readFile('./data.txt', 'utf-8');
 console.log(data);
} catch (err) {
 console.error("读取失败", err);
}
// 实际上你很难在这里显式控制 file close,完全依赖 runtime
}

Python 写法(推荐):

# 不需要 async,不需要 try-catch 来关闭文件
try:
 # 'r' 表示 read (只读), encoding='utf-8' 必加!
 with open('./data.txt', 'r', encoding='utf-8') as f:
     content = f.read()
     # 也可以逐行读取,适合大文件
     # lines = f.readlines()
     print(content)
 # 出了这个缩进,文件自动关闭 (f.close() 被自动调用)
except FileNotFoundError:
 print("文件找不到")

2. JSON 数据解析与生成:前端最熟悉的陌生人

JSON 是前后端和 AI 配置文件的通用语言。Python 内置了 json 模块,用法与 JS 的 JSON 对象有一一对应的关系,但有两组 API 需要区分:处理字符串和处理文件

操作 JavaScript Python (处理字符串) Python (处理文件流)
反序列化 (Parse) JSON.parse(str) json.loads(str) json.load(file_obj)
序列化 (Stringify) JSON.stringify(obj) json.dumps(obj) json.dump(obj, file_obj)

金牌讲师记忆口诀

  • s 的 (loads, dumps) 是处理 String 的(和 JS 一样)。
  • 不带 s 的 (load, dump) 是直接对接文件流的(这是 Python 特色,少写一步读取/写入操作)。

实战代码:

import json

# 1. 模拟从 API 拿到的 JSON 字符串
json_str = '{"name": "DeepSeek", "type": "LLM", "version": 2}'

# JS: JSON.parse(json_str)
data = json.loads(json_str) 
print(data['name'])  # 输出: DeepSeek

# 2. 将字典保存为 JSON 文件
config = {
    "batch_size": 64,
    "learning_rate": 0.001,
    "model_path": "./checkpoints",
    "notes": "这是中文注释"
}

# 写入文件
with open('config.json', 'w', encoding='utf-8') as f:
    # ensure_ascii=False 是关键!
    # 否则中文会被转义成 \uXXXX 这种乱码,前端看着会很难受
    # indent=4 相当于 JS 的 JSON.stringify(obj, null, 4)
    json.dump(config, f, ensure_ascii=False, indent=4)

print("配置已保存!")

5.2 日期与时间处理

JS 的原生 Date 对象可谓是“臭名昭著”,月份从 0 开始(0 是 1 月),格式化日期还得手写或者求助 moment.js / day.js

Python 的 datetime 模块虽然稍显复杂,但逻辑非常严密,且不依赖第三方库也能做很多事。

1. 基础时间操作:datetime vs Date

Python 的 datetime 模块下有几个核心类:datetime (日期+时间), date (只含日期), time (只含时间), timedelta (时间差)。

from datetime import datetime, timedelta

# 获取当前时间
now = datetime.now()
print(f"当前时间: {now}")

# 格式化时间 (String Format Time) -> JS 的 moment().format('YYYY-MM-DD')
# 注意:Python 用的是 %Y %m %d 这种占位符
formatted = now.strftime("%Y-%m-%d %H:%M:%S")
print(f"格式化后: {formatted}")

# 解析时间字符串 (String Parse Time)
str_time = "2023-10-01 12:00:00"
dt_obj = datetime.strptime(str_time, "%Y-%m-%d %H:%M:%S")
print(f"解析回对象: {dt_obj.year}{dt_obj.month}月") # 月份是 10!不是 9!👏

# 时间计算:推算 3 天后的时间
# JS 需要: new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000) ... 心累
future = now + timedelta(days=3, hours=2)
print(f"三天两小时后: {future}")

2. 时区处理:pytz 的应用

在 AI 训练日志和后端服务中,时区(Timezone)非常重要。默认的 datetime.now() 经常是“Naive”(无时区信息的),这在跨国服务器部署时是灾难。

虽然 Python 3.9+ 引入了 zoneinfo,但在很多老项目和教程中,pytz 依然是王者。

前端痛点解决:JS 里的时区处理通常很头大,Python 通过显式指定 tzinfo 来解决。

import pytz
from datetime import datetime

# 1. 获取 UTC 时间 (标准做法)
utc_now = datetime.now(pytz.utc)
print(f"UTC 时间: {utc_now}")

# 2. 转换为北京时间 (Asia/Shanghai)
beijing_tz = pytz.timezone('Asia/Shanghai')
local_time = utc_now.astimezone(beijing_tz)
print(f"北京时间: {local_time}")

# 3. 创建特定时区的时间
# 警告:不要直接用 datetime(..., tzinfo=beijing_tz),容易出 Bug
# 推荐用 localize 方法
naive_dt = datetime(2023, 1, 1, 12, 0, 0)
correct_dt = beijing_tz.localize(naive_dt)
print(f"正确的带时区时间: {correct_dt}")

小结:

  • 文件读写必用 with open
  • JSON 处理记住 loads (String) 和 load (File) 的区别,中文记得关掉 ensure_ascii
  • 时间计算用 timedelta,跨时区转换请认准 pytzzoneinfo

6. 用 FastAPI 构建简单项目

在这一章,我们要把 Python 变成你手中的瑞士军刀。我们将采用企业级分层架构(Controller-Service-Data Layer),这与你熟悉的 NestJS 或大型 React 项目的组织方式是一致的。

📂 企业级项目目录结构

首先,别把代码全写在一个文件里。一个标准的 AI 后端服务应该是这样的:

my_ai_project/
├── app/
│   ├── main.py              # [入口] 类似于 index.ts / App.tsx
│   ├── api/                 # [路由层] 类似于 routes / controllers
│   │   └── v1/
│   │       └── endpoints/
│   │           └── user.py  # 用户模块的接口
│   ├── schemas/             # [数据模型] 类似于 TS Interfaces / DTOs
│   │   └── user.py
│   ├── core/                # [配置] 环境变量、安全设置
│   └── db/                  # [数据库] ORM设置
└── requirements.txt         # [依赖] 类似于 package.json

6.1 FastAPI 快速上手
1. 入口文件:启动你的“服务端 App”

我们先看 app/main.py。这是整个后端的枢纽。

# app/main.py
from fastapi import FastAPI
from app.api.v1.endpoints import user  # 导入我们需要挂载的路由模块

# 1. 初始化应用
app = FastAPI(
    title="AI Backend Service",
    description="为前端赋能的 Python 服务",
    version="1.0.0"
)

# 2. 注册路由
# include_router 类似于 Express 的 app.use('/users', userRouter)
# tags=["users"] 会在 Swagger 文档中把这些接口归类到一个折叠面板下
app.include_router(user.router, prefix="/api/v1/users", tags=["users"])

# 3. 根路由
@app.get("/")
async def root():
    return {"message": "Hello Front-End Developers!"}

👨‍💻 前端视角解读:

  • app = FastAPI() 就像 const app = express()const app = await NestFactory.create(AppModule)
  • Python 的装饰器 @app.get("/") 就像 NestJS 的 @Get('/'),它把下面的函数绑定到了 HTTP GET 请求上。

2. 路由系统:路径参数与查询参数

接下来进入核心业务层 app/api/v1/endpoints/user.py。前端最关心的就是:怎么接 URL 里的参数?

# app/api/v1/endpoints/user.py
from fastapi import APIRouter, Query, HTTPException
from typing import Optional

# 创建一个路由实例,相当于 express.Router()
router = APIRouter()

# 模拟数据库数据
fake_users_db = [{"id": 1, "name": "Tony"}, {"id": 2, "name": "Jack"}]

# === 场景 A:获取单个用户 (路径参数) ===
# URL: GET /api/v1/users/1
@router.get("/{user_id}") 
async def read_user(user_id: int):  # <--- 重点看这里
    """
    获取特定用户信息
    """
    # 逻辑解析:
    # 1. user_id: int -> FastAPI 会自动读取 URL 中的 user_id。
    # 2. 它是强类型的!如果你传 /users/abc,FastAPI 直接报错,不会进入函数。
    # 3. 这里 user_id 已经是数字类型,不用像 JS 那样 parseInt(id) 了。
    
    # 模拟查询
    user = next((u for u in fake_users_db if u["id"] == user_id), None)
    
    if not user:
        # 抛出异常,类似于 throw new Error(),但会直接转为 404 HTTP 响应
        raise HTTPException(status_code=404, detail="User not found")
        
    return user

# === 场景 B:搜索用户 (查询参数) ===
# URL: GET /api/v1/users/?keyword=jack&limit=10
@router.get("/")
async def search_users(
    keyword: Optional[str] = None, # 默认是 None,表示可选
    limit: int = Query(10, gt=0, le=100) # 默认10,且必须 0 < limit <= 100
):
    """
    搜索用户列表
    """
    # 逻辑解析:
    # 1. 函数参数中,没有在路径里声明的变量(如 keyword),自动被视为 ?query= 参数。
    # 2. Query(...) 是一个强大的校验器。如果前端传 limit=200,API 会直接拒绝请求。
    # 3. 这里的校验逻辑,如果在 Node.js 里写,可能需要好几行 if/else。
    
    return {"keyword": keyword, "limit": limit, "data": fake_users_db[:limit]}

👨‍💻 前端视角解读:

  • 不再需要手动写 const { id } = req.paramsconst { limit } = req.query
  • 你只需要在函数参数里定义好变量名类型,FastAPI 帮你完成取值、转换、校验三步走。

3. 异步数据库操作(对标 ORM)

在 AI 项目中,数据库操作必须是异步的(Async),否则一个慢查询会卡死整个服务。这里我们用 SQLAlchemy(Python 版的 TypeORM)的异步模式。

# 假设这是 app/services/user_service.py
from sqlalchemy.future import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.user import User # 导入 ORM 模型

async def get_user_by_email(db: AsyncSession, email: str):
    # 1. 构建查询语句 (类似于 TypeORM 的 QueryBuilder)
    # SQL: SELECT * FROM users WHERE email = '...'
    stmt = select(User).where(User.email == email)
    
    # 2. 执行查询 (注意 await)
    # 这完全等同于 JS 的: const result = await db.query(...)
    result = await db.execute(stmt)
    
    # 3. 获取结果
    # scalars().first() 相当于取数组的第一个元素
    return result.scalars().first()

6.2 使用 Pydantic:更优雅的参数校验

这是 Python 后端最让前端羡慕的功能。
在 Node.js 中,你可能需要用 JoiZodclass-validator 来校验 POST 请求的 Body。
在 FastAPI 中,这叫做 Pydantic

1. 定义 DTO (Data Transfer Object)

我们创建一个 app/schemas/user.py。这不仅仅是类型定义,它是运行时校验规则

# app/schemas/user.py
from pydantic import BaseModel, EmailStr, Field
from typing import Optional

# === 接收前端 POST 数据的模型 (Request DTO) ===
class UserCreate(BaseModel):
    # EmailStr: Pydantic 会自动正则校验这是否是个邮箱
    email: EmailStr 
    
    # Field: 类似于 Zod 的 .min(6).max(20)
    password: str = Field(..., min_length=6, max_length=20, description="密码")
    
    # Optional: 可选字段
    age: Optional[int] = Field(None, ge=18, description="年龄必须大于18")

# === 返回给前端的数据模型 (Response DTO) ===
class UserResponse(BaseModel):
    id: int
    email: EmailStr
    is_active: bool
    
    # Config: 允许 Pydantic 直接读取 ORM 对象的数据
    class Config:
        from_attributes = True 
2. 在 Controller 中使用 Pydantic

回到 app/api/v1/endpoints/user.py,看看如何处理 POST 请求。

from app.schemas.user import UserCreate, UserResponse

# response_model=UserResponse: 
# 告诉 FastAPI,不论我的函数里 return 了什么乱七八糟的对象,
# 最终返回给前端 JSON 时,只保留 UserResponse 里定义的字段。
# (这一步自动帮你把 password 等敏感字段过滤掉了!)
@router.post("/", response_model=UserResponse)
async def create_user(
    user_in: UserCreate # <--- 核心魔法
):
    """
    注册新用户
    """
    # 逻辑解析:
    # 1. 在进入这个函数之前,FastAPI 已经拿到了 Request Body。
    # 2. 它依照 UserCreate 的规则进行了严格校验(邮箱格式?密码长度?)。
    # 3. 如果校验失败,直接返回 422 错误,函数体根本不会执行。
    # 4. 如果成功,user_in 就是一个对象,你可以用 user_in.email 直接访问。
    
    # 模拟数据库保存操作
    # db_user = await service.create(user_in) 
    
    # 模拟返回数据
    fake_db_obj = {
        "id": 88, 
        "email": user_in.email, 
        "password": "hashed_secret", # 注意:这个字段会被 response_model 自动过滤掉
        "is_active": True
    }
    
    return fake_db_obj

👨‍💻 金牌教师总结:

  • Type Hints (类型提示) 不仅仅是像 TypeScript 那样给 IDE 看的,在 FastAPI 里,它们是真实生效的逻辑
  • Pydantic 让你告别了代码里到处充斥的 if (!body.email) return error 这种防御性代码。
  • Response Model 则是你的安全守门员,防止后端不小心把整个 User 对象(包含密码)直接 JSON.stringify 给前端。

掌握了这一节,你就拥有了构建 AI 业务逻辑(比如接收用户 Prompt,调用 OpenAI,返回结果)的坚实骨架。下一步,我们就可以往这个架构里填充 AI 代码了!

好的,收到!作为金牌讲师,我深知**“光说不练假把式”**。代码写得再漂亮,跑不起来也是白搭。

这一节,我们补全最后一块拼图:如何安装依赖、启动服务,并进行真实的 API 调用测试

我们将沿用上一节定义的 企业级目录结构


6.3 项目启动与接口实测

1. 准备依赖环境

在 Node.js 中,你有 package.json;在 Python 中,我们通常使用 requirements.txt

在项目根目录下创建一个 requirements.txt 文件:

fastapi==0.109.0
uvicorn[standard]==0.27.0  # ASGI 服务器,相当于 Node
pydantic==2.6.0            # 数据校验
pydantic-settings==2.1.0   # 配置管理
sqlalchemy==2.0.25         # ORM
asyncpg==0.29.0            # 异步 PostgreSQL 驱动

安装依赖命令(终端执行):

# 建议先创建虚拟环境 (类似于在项目里放一个独立的 node_modules)
python -m venv venv

# 激活虚拟环境 (Mac/Linux)
source venv/bin/activate
# 激活虚拟环境 (Windows)
# venv\Scripts\activate

# 安装包
pip install -r requirements.txt

2. 启动服务

在 Node.js 中,你可能会用 npm run dev (底层可能是 nodemon server.js)。
在 FastAPI 中,我们需要使用 Uvicorn 这个高性能 ASGI 服务器来运行我们的 App。

启动命令(在项目根目录执行):

# app.main:app 解析:
# app.main -> 对应 app/main.py 文件
# :app     -> 对应文件中创建的 app = FastAPI() 对象
# --reload -> 开启热更新(改代码自动重启),开发必备!

uvicorn app.main:app --reload

当看到如下输出,说明服务启动成功:

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [12345] using WatchFiles
INFO:     Application startup complete.

3. 接口实测(Request Examples)

现在服务跑在 http://127.0.0.1:8000。我们来看看如何请求上一节写的那些接口。

为了方便演示,我提供了 curl 命令,你可以直接在终端粘贴运行。当然,你也可以用 PostmanApifox

场景 A: 获取根路径
  • 方法: GET
  • URL: http://127.0.0.1:8000/
curl -X 'GET' \
  'http://127.0.0.1:8000/' \
  -H 'accept: application/json'

预期响应:

{
  "message": "Hello Front-End Developers!"
}

场景 B: 获取特定用户(路径参数)
  • 方法: GET
  • URL: http://127.0.0.1:8000/api/v1/users/1
curl -X 'GET' \
  'http://127.0.0.1:8000/api/v1/users/1' \
  -H 'accept: application/json'

预期响应:

{
  "id": 1,
  "name": "Tony"
}

测试错误情况(类型检查):
如果你传字符串 abc 给需要 int 的接口:
curl -X 'GET' 'http://127.0.0.1:8000/api/v1/users/abc'
预期响应 (FastAPI 自动处理):

{
  "detail": [
    {
      "type": "int_parsing",
      "loc": ["path", "user_id"],
      "msg": "Input should be a valid integer",
      "input": "abc"
    }
  ]
}

场景 C: 创建用户(POST + JSON Body + Pydantic 校验)

这是最能体现 FastAPI 强大的地方。

  • 方法: POST
  • URL: http://127.0.0.1:8000/api/v1/users/
  • Header: Content-Type: application/json

请求命令:

curl -X 'POST' \
  'http://127.0.0.1:8000/api/v1/users/' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "email": "frontend@example.com",
  "password": "mysecretpassword",
  "age": 25
}'

预期响应:
注意看!password 字段不见了,因为我们在 UserResponse 里把它过滤了。而且自动添加了 idis_active

{
  "id": 88,
  "email": "frontend@example.com",
  "is_active": true
}

测试校验失败(密码太短):
如果把 "password" 改成 "123" (我们在代码里限制了 min_length=6):
预期响应:

{
  "detail": [
    {
      "type": "string_too_short",
      "loc": ["body", "password"],
      "msg": "String should have at least 6 characters",
      "ctx": {"min_length": 6}
    }
  ]
}

4. 终极神器:Swagger UI 文档

作为前端,最烦的就是后端改了接口不告诉我们,或者文档更新不及时。

FastAPI 内置了 Swagger UI。服务启动后,直接在浏览器访问:

👉 http://127.0.0.1:8000/docs

你会看到一个交互式的文档页面:

  1. 所见即所得:所有 API 自动列出。
  2. Schema 可视化:点开 Schemas,可以看到 UserCreateUserResponse 的具体字段定义。
  3. 在线调试:点击 “Try it out” 按钮,直接在浏览器里发送请求,连 curl 都不用写!

Swagger UI 示意图


金牌教师总结

到现在为止,你已经完成了一个符合企业级规范的后端微服务雏形:

  1. 目录结构:分层清晰,拒绝面条代码。
  2. 依赖管理requirements.txt 明确环境。
  3. 开发体验uvicorn 热重载,写完代码直接生效。
  4. 接口测试:全自动的 Swagger 文档,前后端联调效率翻倍。

这就是为什么 AI 领域的开发(从 OpenAI 的官方示例到企业内部的中台)都首选 FastAPI 的原因。准备好进入下一阶段了吗?我们要开始往里面塞 AI 模型了!

“做 AI 应用不就是前端直接 fetch OpenAI 的 API 吗?”
错!大错特错!

如果在前端直接调用:

  1. API Key 裸奔:你的 Key 会直接暴露在浏览器 Network 面板里,任何人都能拿去刷爆你的信用卡。
  2. 毫无记忆:原生 API 是无状态的,你必须在后端维护“对话历史(Context)”。
  3. 没有大脑:你没法做 Prompt Engineering(提示词工程)、没法挂载知识库(RAG),也没法做权限控制。

今天我们要用 FastAPI + Poetry 构建一个企业级 AI 中台服务。这不仅仅是转发请求,而是构建一个AI 系统


7. 构建 AI 核心服务:从调用到系统

7.1 环境治理:使用 Poetry 管理依赖

在上一节我们用了 requirements.txt,那属于“老派”做法。在企业级 Python 项目中,Poetry 才是王者。

👉 前端对标

  • pip = npm (早期版本,没有 lock 文件,容易版本冲突)
  • Poetry = npm / yarn + package.json + node_modules 管理。它有清晰的依赖树和 Lock 文件。
1. 初始化 Poetry 项目

假设你已经在项目根目录:

# 1. 安装 Poetry (如果未安装)
curl -sSL https://install.python-poetry.org | python3 -

# 2. 初始化项目 (生成 pyproject.toml,类似 package.json)
poetry init 

# 3. 安装 AI 相关依赖
# openai: 官方 SDK
# python-dotenv: 用于读取 .env 环境变量
# tiktoken: 计算 Token 数量(OpenAI 计费相关)
poetry add fastapi uvicorn[standard] pydantic pydantic-settings openai python-dotenv tiktoken

现在你的项目里多了一个 pyproject.toml,这比 txt 文件优雅多了。


7.2 企业级 AI 架构设计

我们需要在之前的架构上扩展 AI Service Layer。请注意,我们绝对不会在 Controller(路由层)里直接写 OpenAI 的调用代码。

📂 升级后的目录结构:

my_ai_project/
├── app/
│   ├── api/
│   │   └── v1/
│   │       └── endpoints/
│   │           └── chat.py      # [Controller] 处理 HTTP 请求和流式响应
│   ├── core/
│   │   └── config.py            # [Config] 统一管理 API KEY
│   ├── services/
│   │   └── llm_service.py       # [Service] AI 核心逻辑封装 (Prompt/Model)
│   ├── schemas/
│   │   └── chat.py              # [DTO] 定义对话的输入输出格式
│   └── main.py
├── .env                         # 存放 OPENAI_API_KEY
├── poetry.lock
└── pyproject.toml

7.3 编写核心 AI 服务层 (Service Layer)

我们需要封装一个 LLMService。这个类的作用是隔离底层模型差异。今天你用 GPT-4,明天老板想换 Claude 3 或者国产大模型,只需要改这个文件,Controller 层完全不用动。

文件:app/services/llm_service.py

import os
from openai import AsyncOpenAI
from typing import AsyncGenerator

# 加载环境变量
from dotenv import load_dotenv
load_dotenv()

class LLMService:
    def __init__(self):
        # 初始化 OpenAI 异步客户端
        # 为什么用异步?因为 LLM 响应很慢,同步会阻塞整个服务器线程!
        self.client = AsyncOpenAI(
            api_key=os.getenv("OPENAI_API_KEY"),
            # 如果用国内中转,这里可以配置 base_url
            # base_url="https://api.openai-proxy.com/v1" 
        )

    async def chat_stream(self, messages: list) -> AsyncGenerator[str, None]:
        """
        流式对话生成器
        前端最喜欢的 "打字机效果" 就是靠这个实现的
        """
        try:
            # 发起流式请求
            stream = await self.client.chat.completions.create(
                model="gpt-3.5-turbo", # 或者 gpt-4
                messages=messages,
                stream=True,          # <--- 重点:开启流式模式
                temperature=0.7,      # 控制创造性
                max_tokens=1000
            )

            # 逐块(chunk)返回数据
            async for chunk in stream:
                content = chunk.choices[0].delta.content
                if content:
                    yield content  # <--- Python 的 yield,相当于生成器
                    
        except Exception as e:
            print(f"OpenAI API Error: {str(e)}")
            yield f"Error: {str(e)}"

# 单例模式导出,避免重复实例化
llm_service = LLMService()

👨‍💻 金牌讲师解析:

  • AsyncOpenAI: 必须用异步客户端。如果 100 个人同时请求,同步代码会排队,异步代码会并行处理。
  • yield: 这里使用了 Python 的 AsyncGenerator。它就像一个水龙头,大模型吐出一个字,我们就往前端“流”一个字。

7.4 定义数据交互标准 (Schemas/DTO)

前端发给后端的 JSON 格式,必须严格定义。

文件:app/schemas/chat.py

from pydantic import BaseModel
from typing import List, Optional

class Message(BaseModel):
    role: str  # "user" | "assistant" | "system"
    content: str

class ChatRequest(BaseModel):
    messages: List[Message]
    # 可以在这里加参数控制,比如 temperature
    temperature: Optional[float] = 0.7 

7.5 实现流式接口 (Controller Layer)

这是最关键的一步。普通的 return JSON 是等 AI 说完一整段话才返回,用户体验极差。我们要用 SSE (Server-Sent Events) 技术。

文件:app/api/v1/endpoints/chat.py

from fastapi import APIRouter
from fastapi.responses import StreamingResponse
from app.schemas.chat import ChatRequest
from app.services.llm_service import llm_service

router = APIRouter()

@router.post("/completions")
async def chat_completions(request: ChatRequest):
    """
    AI 对话接口 (流式响应)
    """
    # 1. 可以在这里做 Prompt Engineering (提示词注入)
    # 比如:强制在用户消息前加一段 "你是一个金牌前端讲师..." 的 system prompt
    
    # 2. 调用 Service 层获取生成器
    generator = llm_service.chat_stream(
        messages=[msg.model_dump() for msg in request.messages]
    )

    # 3. 返回流式响应
    # media_type="text/event-stream" 是标准的 SSE 格式
    return StreamingResponse(generator, media_type="text/event-stream")

7.6 启动与实战测试

确保你在 .env 文件里填好了 Key:

OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx

启动服务 (使用 Poetry 环境):

poetry run uvicorn app.main:app --reload

👉 如何测试流式响应?

普通的 curl 也能看到效果,你会看到字是一个个蹦出来的,而不是卡顿很久一次性显示。

curl -X 'POST' \
  'http://127.0.0.1:8000/api/v1/users/completions' \
  -H 'accept: text/event-stream' \
  -H 'Content-Type: application/json' \
  -d '{
  "messages": [
    {"role": "system", "content": "你是一个助手"},
    {"role": "user", "content": "请用100字介绍一下FastAPI"}
  ]
}'

从“调用 AI”到“构建 AI 系统”的进化

作为架构师,我要提醒你,刚才的代码只是起点。真正的 AI 系统(这也是你下一阶段要学习的方向)是在 LLMService 里做文章:

  1. Prompt Management (提示词管理)

    • 你不会想把 Prompt 写死在代码里。你需要一个模板系统,比如 PromptTemplate
    • 例子:把用户的 “介绍 FastAPI” 包装成 “请以资深架构师的身份,用幽默的语气介绍 FastAPI”。
  2. Context Window (上下文管理)

    • 大模型记不住你说过的话。你需要把历史聊天记录存在数据库(PostgreSQL/Redis)里,每次请求时取出来,塞进 messages 列表发给 OpenAI。
  3. RAG (检索增强生成)

    • 当用户问 “我们公司的请假流程是什么?” GPT-4 不知道。
    • 你需要先去向量数据库查文档,把相关段落作为 Context 塞给 AI。这就是目前最火的 RAG 架构

总结

  • python是动态类型语言,变量定义直接用 变量名=xx即可。

  • python天然支持模块化,可以通过from 模块名 import 方法1,方法2等方法导入模块

  • fastApi相当于nest,其用法也很相像,也是MVC架构,使用类似于ts的装饰器,最简单的例子

    一,创建根服务 (main.module.ts)

    # app/main.py
    from fastapi import FastAPI
    from app.api.v1.endpoints import users
    from app.api.v1.endpoints import chat
    app = FastAPI(
        title="AI Backend Service",
        description="企业级 FastAPI 项目模板",
        version="1.0.0"
    )
    
    # 注册路由,类似于 express 的 app.use('/users', userRouter)
    app.include_router(chat.router, prefix="/api/v1/chat", tags=["chat"])
    @app.get("/")
    async def root():
        return {"message": "Service is running correctly"}
    

    二 创建controller层

    在app/api/v1/endpoints下创建chat.py文件

    from fastapi import APIRouter
    from fastapi.responses import StreamingResponse
    from app.schemas.chat import ChatRequest
    from app.services.llm_service import llm_service
    
    router = APIRouter()
    
    @router.post("/completions")
    async def chat_completions(request: ChatRequest):
        """
        AI 对话接口 (流式响应)
        """
        # 1. 可以在这里做 Prompt Engineering (提示词注入)
        # 比如:强制在用户消息前加一段 "你是一个金牌前端讲师..." 的 system prompt
        
        # 2. 调用 Service 层获取生成器
        generator = llm_service.chat_stream(
            messages=[msg.model_dump() for msg in request.messages]
        )
    
        # 3. 返回流式响应
        # media_type="text/event-stream" 是标准的 SSE 格式
        return StreamingResponse(generator, media_type="text/event-stream")
    

    创建router,定义url参数,ChatRequest类似于nest的class_vate校验。

    三 创建se rvice层

    在app/services下创建llm_service.py文件

    import os
    from openai import AsyncOpenAI
    from typing import AsyncGenerator
    
    # 加载环境变量
    from dotenv import load_dotenv
    load_dotenv()
    
    class LLMService:
        def __init__(self):
            # 初始化 OpenAI 异步客户端
            # 为什么用异步?因为 LLM 响应很慢,同步会阻塞整个服务器线程!
            self.client = AsyncOpenAI(
                api_key=os.getenv("OPENAI_API_KEY"),
                # 如果用国内中转,这里可以配置 base_url
                base_url="http://localhost:11434/v1" ,
            )
    
        async def chat_stream(self, messages: list) -> AsyncGenerator[str, None]:
            """
            流式对话生成器
            前端最喜欢的 "打字机效果" 就是靠这个实现的
            """
            try:
                # 发起流式请求
                stream = await self.client.chat.completions.create(
                    model="llama3.2", # 或者 gpt-4
                    messages=messages,
                    stream=True,          # <--- 重点:开启流式模式
                    temperature=0.7,      # 控制创造性
                    max_tokens=1000
                )
    
                # 逐块(chunk)返回数据
                async for chunk in stream:
                    content = chunk.choices[0].delta.content
                    if content:
                        yield content  # <--- Python 的 yield,相当于生成器
                        
            except Exception as e:
                print(f"OpenAI API Error: {str(e)}")
                yield f"Error: {str(e)}"
    
    # 单例模式导出,避免重复实例化
    llm_service = LLMService()
    

    写业务方法,供controller使用。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐