Skip to content

AGI全栈项目 ​

LLMOps项目架构 ​

| --- | --- | | 表示层 | PC网站、开放API、APP | | 接入层 | Nginx、负载均衡 | | 控制层 | AI应用模块、用户模块、授权模块、插件模块 | | 服务层 | LLM应用服务、上传服务、授权服务、文本嵌入服务 | | 核心层 | 多LLM集成、Agent集成、LangChain封装 | | 存储层 | PostgreSQL、Redis、向量数据库、对象存储 | | 资源层 | Linux服务器、Docker虚拟机、K8s集群 |

技术栈

  1. 使用 Python 作为后端编程语言虽然性能比不上Java等编译型等语言,但是Python丰富的生态系统,庞大的AI开源模型、机器学习基础库等,是其他语言无法媲美的。
  2. Flask是一个轻量级的框架,核心非常小,功能丰富但不臃肿,性能对比Django强了不少,零基础也可以快速入门,大大降低了学习难度。
  3. 对于LLM应用开发,可以使用LangChain极大简化开发流程,提升开发效率,其他语言需要自己封装轮子。
  4. 使用 Postgres 数据库
  5. PyTest 单元测试

环境搭建 ​

后端搭建 ​

  1. Python虚拟环境的创建与优缺点
  • 创建虚拟环境:python -m venv <环境名>,该命令在Python 2.7+/Python3.3+以上版本可用
  • python 虚拟环境与系统环境之间是隔离的,不会相互干扰,不会对系统环境造成任何影响,最大程度避免包冲突
  • 占用更大的空间
    • 激活虚拟环境:activate(cmd env\Scripts\activate)
    • 退出虚拟环境:deactivate(cmd env\Scripts\deactivate.bat)
  1. 腾讯云pip镜像加速装置
  1. python 新建代码模版
#! /usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time    : ${DATE} ${TIME}
@Author  : liuhuakawaii@gmail.com
@File    : ${NAME}.py
"""
  1. 插件推荐
  • .env files
  • CodeGlance Pro

项目目录结构约定、规范与依赖注入 ​

Flask是一个优美的微框架,这即是一件好事--你可以按照自己的习惯和想法来组织你的项目,不过对于团队来说这可能是一件坏事--团队每个人都有自己的喜好,这会使整体项目的结构很混乱。因此我们为LLMOp项目设计提炼了一种规范,它不仅仅是结构,风格还还有诸多细节

  1. 项目结构 对于很多人来说,适应规范会觉得很不舒服,但在开发中,这是一件必须的事,越规矩,越自由。
text
|---app  // 应用入口集合
|    ├── __init__.py
│    └── http
│---config  // 应用配置文件
│   ├── __init__.py
│   ├── config.py
│   └── default_config.py
│---internal  // 应用所有内部文件
│   └── core  // LLM核心文件,集成LangChain, LLM, Embedding等模型的代码
│       ├── agent
│       ├── chain
│       ├── prompt
│       ├── model_runtime
│       ├── moderation
│       ├── tool
│       ├── vector_store
│       └── ...
│   └──exception  // 通用公共异常目录
│       ├── __init__.py
│       ├── exception.py
│       └── ...
│   └──extension  // Flask扩展文件目录
│       ├── __init__.py
│       ├── database_extension.py
|       └── ...
|   └──handler  // 路由处理器、控制器目录
│       ├── __init__.py
│       ├── account_handler.py
│       └── ...
│   └──middleware  // 应用中间件目录,包含检验是否登录
│       ├── __init__.py
│       ├── middleware.py
|       └── ...
|   └──migration  // 数据库迁移文件目录,自动生成
│       ├── versions
|       └── ...
|   └──model  // 数据库模型文件目录
│       ├── __init__.py
│       ├── account.py
|       └── ...
|   └──router  // 应用路由文件夹
│       ├── __init__.py
│       └── router.py
│   └──schedule  // 调度任务、定时任务文件夹
│       ├── __init__.py
│       └── ...
│   └──schema  // 请求和响应的结构体
│       └── __init__.py
│   └──server  // 构建的应用,与app文件夹对应
│       └── __init__.py
│   └──service  // 服务层文件夹
│       ├── __init__.py
│       └── oauth_service.py
│   └──task  // 任务文件夹,支持即时任务+延迟任务
│       └── __init__.py
│---pkg  // 扩展包文件夹
│   ├── __init__.py
│   └── oauth
│       ├── __init__.py
│       └── github_oauth.py
|---storage  // 本地存储文件夹
|---test  // 测试目录(注意在settings里面排除)
|---venv  // 虚拟环境
|---.env  // 应用配置文件
|---.gitignore  // 配置git忽略文件
|---requirements.txt  // 第三方包依赖管理
|---README.md  // 项目说明文件
  1. 代码运行流程图 代码运行流程图
  2. 文件与Python类函数命名规范 3.1文件名
  • 使用全部小写字母。
  • 使用下划线分隔单词,例如:app_service.py。
  • 尽可能保证文件作用的单一,不要把所有代码一次性写在同一个文件中。 3.2类名
  • 使用驼峰命名法,例如:class AppService。
  • 类名应该以大写字母开头,每个单词的首字母都大写。
  • 如果类名由多个单词组成,单词之间不使用下划线分割。 3.3函数名和方法
  • 使用小写字母。
  • 使用下划线分隔单词。
  • 例如:get_account,generate_token等。 3.4变量名
  • 使用小写字母。
  • 使用下划线分隔单词。
  • 例如:my_variable,token_count。 3.5常量
  • 使用全大写字母。
  • 使用下划线分隔单词。
  • 例如:MAX SIZE,PI等。 3.6私有变量与方法
  • 以一个下划线开头表示私有,例如:my_private_variable,_my_private_method()等
  • 在Python中并没有严格的私有变量/方法,这种命名约定只是一种约定,而不是强制规则,实际上这些变量/方法仍然可以被使用,但是作为一种约定,在外部调用时,不应该调用私有的变量与方法。 3.7模块名
  • 与文件名类似,使用全部小写字母,使用下划线分隔单词。
  • 例如:my_module.py对应的模块名应该是my_module。
  • 模块下创建_init_.py文件代表当前目录为一个模块,并尽可能在_init_.py中使用 __all__ 简化导出。
  1. Python中的依赖注入
  • 依赖注入是软件工程中的一种设计模式,它允许在创建对象时由夕下部提供依赖关系,而不是自己创建这些依赖关系。
  • 简单来说,即我需要什么,传递什么,而不是自己内部构建,Corntroller需要使用Service的内容,将Service作为参数传递给Controller即可,在Python中可以使用injector包来快速实现依赖注
  • 安装:pip install injector
  • 使用示例:
python
from injector import Injector, inject


class A:
    name: str = 'llmops'


@inject
class B:
    def __init__(self, a: A):
        self.a = a

    def print(self):
        print(f"Class A的name:{self.a.name}")


injector = Injector()
b = injector.get(B)
b.print()

其中@inject装饰器用于注入,使用@inject装饰的函数/类,不需要显式创建依赖对象就可以直接使用。要获取被注入的对象,可以使用Injector实例的get0方法传入特定的类型,即可获取。 5. 获取requirements.txt

  • 运行pip freeze > requirements.txt命令即可。
  • 使用 pipreqs --ignore env --force 命令
bash
pip install --no-deps pipreqs
pip install yarg==0.1.9 docopt==0.6.2
  • 安装依赖:pip install -r requirements.txt

PostgresSQL pgAdmin4连接密码问题

  1. 修改 pg_hba.conf:
text
host    all             all             127.0.0.1/32            trust
host    all             all             ::1/128                 trust
  1. 重启 PostgreSQL 服务:
bash
# Linux / macOS
sudo systemctl restart postgresql

# Windows
net stop postgresql-x64-17
net start postgresql-x64-17
  1. windows 下关闭 PostgreSQL 自动启动.win+r services.msc

30行代码实现一个聊天机器人接口 ​

  1. 安装 openai:pip install --upgrade openai
  2. 核心代码部分:
python
from flask import request
from openai import OpenAI


class AppHandler:
    """应用控制器"""

    def completion(self):
        """聊天接口"""
        # 1.提取从接口中获取的输入
        query = request.json.get("query")
        # 2.构建OpenAI客户端,并发起请求
        client = OpenAI(
            api_key="<your api key>",
            base_url="https://api.moonshot.cn/v1",
        )
        # 3.得到请求响应,然后将OpenAI的响应传递给前端
        completion = client.chat.completions.create(
            model="kimi-k2-0905-preview",
            messages=[
                {"role": "system",
                 "content": "你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。同时,你会拒绝一切涉及恐怖主义,种族歧视,黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。"},
                {"role": "user", "content": query}
            ],
            temperature=0.6,
        )
        # 通过 API 我们获得了 Kimi 大模型给予我们的回复消息(role=assistant)
        content = completion.choices[0].message.content
        return content
  1. 将.env加载到环境变量实现配置分离
  • 在Python项目中,如果想将.env文件的信息加载成环境变量,可以使用python-dotenv库。pip install python-dotenv
  • openai默认读取的是apikey from OPENAI_API_KEY,organization from OPENAI_ORG_ID,如果你的命名不一致则可以通过os.getenv('<youkey>')来获取在环境变量中的配置信息,将代码与配置分离,方便后期维护。
  1. 使用Flask-wtf对数据进行验证
  • pip install -U Flask-WTF

统一响应接口设计与实现 ​

HTTP响应状态码,无论是成功还是失败,全部设置为200,通过业务状态码来判断操作是否成功,减少前端的判断。发生错误时,需要在message和data中携带相应的错误与描述信息

  1. 定义接口统一响应格式
json
{
  "code":"success",//响应状态
  "message":"获取AI应用数据成功",//业务消息提示
  "data":{}//业务获取的数据
}

为了简化开发,我们将将业务状态码定义成success、fail、not_found、unauthorized、forbidden、validate_error. message为相应的接口描述,如果业务状态码为非成功状态,需需要添加上相应的描述信息。 2. 接口分页数据响应格式 如果返回的数据为列表型分页数据,则在data中存在两个字段paginator和list,分别代表分页的数据信息,响应的数据列表信息,数据格式如下所示:

json
{
  "code": "success",
  "message":"",
  "data":{
    "paginator":{
      "pagesize":10,//当前页数每页条数
      "current_page":1, //当前页数
      "total_page":10,//总页数
      "total_record":100//总记录条数
    },
    "list":[],//分页的列表数据
  }
}

使用pytest进行单元测试 ​

在Pytest中,测试用例的编写需要遵循以下规则:

  1. 测试文件以test_开头或_test结尾;
  2. 测试类以Test开头,并且不能带有init方法;
  3. 测试函数以test_开头;
  4. 断言使用基本的assert即可; pytest会递归查找当前目录及子目录下的所有以test_开始或者_test结尾的Python脚本,执行其中符合规则的函数和方法,不需要显示调用,一些常见的运行命令:
bash
#直接运行文件夹内符合规则的所有用例
pytest folder_name
#执行某个Python文件中的用例
pytest test_file.py
#执行某个Python文件内的某个函数
pytest test_file.py::test_func
#执行某个Python文件内某个测试类的某个方法
pytest test_file.py::TestClass::test_method
#运行测试时显示标准输出(stdout),允许测试中的print()语句直接输出到终端
pytest -s
#运行测试时显示详细的信息,包括每个测试用例的名称及结果(试通过/失败/跳过等),-v代表verbose
pytest -v

应用ORM模型的创建与增删改查 ​

  1. 创建模型 要想在项目中使用ORM,必须创建对应的类,并继承db.Model,即可实现与对应数据库进行关联。在类中还可以通过_tablename_属性来设置 表名,通过_table_args来为表添加相应的主键index索引、unique索引等约束。其中关于ORM模型的字段类型、索引约束、主键等均可以从sqlalchemy包中导入,从这个包导入的类支持所有的数据库,在切换数据库时不需要进行调整,除此之外怀还可以导入特定数据库特有的结构,例如Postgres中特有的JSONB数据类型,特有的类型在切换数据库时,需要修改代码进行调整。 一些常见的sqlalchemy的数据类型:
python
from sqlalchemy import(
  Column,#用户定义字段
  UUID, #UUID类型,例如:123e4567-e89b-12d3-a456-426655440000
  String,#字符串,对应数据库的varchar
  Text,#长文本类型,对应数据库的text
  DateTime,#事件类型,对应数据库的timestamp
  Integer,#整形,对应数据库的int
  Decimal,#小数型,对应数据库的decimal
  PrimaryKeyConstraint,#创建主键约束
  Index,#创建索引
  UniqueConstraint,#创建唯一值约束
)
from sqlalchemy.dialects.postgresql import JSONB #postgres 特有的jsonb数据,使用二进制存储json,性能更强