基于 FastAPI 搭建 LLM 调用 API 服务教程
本文将基于 GitHub 项目 JerrySilver/AI_call 的源码,介绍如何使用 FastAPI 搭建一个调用大语言模型(LLM)的 API 服务。我们将面向具有 Python 基础、希望构建类似服务的开发者,逐步讲解项目结构和关键模块,包括 FastAPI 服务入口、LLM 接口调用逻辑、接口定义与请求格式、项目配置方式,以及其他支撑模块(工具函数、日志、模型注册等)。希望通过本文,读者可以了解如何快速构建起一个支持调用 OpenAI 模型和本地模型的 FastAPI 服务。
🗂️ 项目结构概览
首先,让我们看一下项目的代码目录结构和各主要模块的作用:
AI_call/ # 项目根目录
├── main.py # FastAPI 服务入口,启动应用和路由注册
├── router/ # 路由模块目录
│ └── llm.py # LLM API 路由定义,处理请求和响应
├── openai_llm.py # LLM 接口调用逻辑封装,支持 OpenAI 和本地模型调用
├── config.py # 项目配置模块,读取API密钥等配置信息
├── utils/ # 工具模块目录
│ ├── app_logger.py # 日志配置与封装
│ └── model_registry.py # (示例)模型注册与管理工具
└── requirements.txt # 项目依赖列表
如上所示,项目采用清晰的分层结构:
- main.py:应用入口,创建 FastAPI 实例并包含路由。
- router/:存放 FastAPI 路由定义,这里
llm.py
定义了与大语言模型调用相关的接口路由。 - openai_llm.py:封装了与 LLM提供方交互的逻辑。例如调用 OpenAI 的 API,或调用本地部署的模型接口。
- config.py:集中管理配置,例如 API Key、本地模型路径、服务端口等,通过读取环境变量或配置文件设置。
- utils/:包含工具函数模块,如日志配置(app_logger.py)和模型注册管理(model_registry.py)等,提供辅助功能。
通过这种结构,代码逻辑清晰分离:主程序负责启动服务和路由挂载,路由处理请求/响应,具体与模型的交互由独立模块负责,配置和工具模块提供支撑。
🚀 FastAPI 服务入口(main.py)
FastAPI 服务入口位于 main.py 文件。它的主要作用是创建 FastAPI 应用实例、配置全局中间件和事件,并将各路由模块包含进应用。以下是 main.py 的关键部分解析:
-
创建应用:初始化 FastAPI 对象
app = FastAPI()
,可以传入应用名称、文档描述等元数据。示例:from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI(title="LLM API Service", description="用于调用大语言模型的API服务")
上述代码创建了一个 FastAPI 应用,并指定了标题和描述。项目可能还启用了 CORS中间件 以允许跨域请求,典型实现是在应用上添加
CORSMiddleware
中间件。 -
注册路由:main.py 会引入我们定义的路由模块并将其包含到应用中。例如:
from router import llm as llm_router ... app.include_router(llm_router.router, prefix="/api/llm")
这里我们将
router/llm.py
中定义的llm_router
注册到应用下,并指定了路由前缀(如/api/llm
)。这样,LLM 相关接口就挂载在/api/llm
路径下,方便分类管理。 -
启动服务:如果 main.py 被直接运行,则通过 Uvicorn 启动服务。通常会写:
if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000, reload=True)
这允许开发者使用
python main.py
启动调试服务器。更常见的做法是在命令行运行uvicorn main:app --reload
来启动服务,其中main:app
指明了应用实例位置。 -
其它初始化:main.py 还可能设置一些全局配置,比如 限流 中间件或事件处理。项目使用了
slowapi
库实现限流功能,这通常通过创建一个Limiter
对象并注册异常处理器来完成。例如:from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded limiter = Limiter(key_func=get_remote_address) app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
以上代码设置根据请求源IP的限流器,并将其挂载到应用状态,注册异常处理。之后可以在路由中使用装饰器为接口添加限流策略(详见下文)。
通过 main.py 的这些设置,我们的 FastAPI 服务就准备就绪:应用实例创建、配置加载、路由注册都完成后,服务即可对外提供 API。
🤖 LLM 接口调用逻辑(openai_llm.py)
LLM 调用的核心逻辑封装在 openai_llm.py 模块中。它提供统一的接口来调用不同的大语言模型供应商,例如 OpenAI 的 GPT 系列模型,或者本地部署的模型。下面介绍该模块的要点:
-
配置 API Key:由于调用 OpenAI 接口需要 API密钥,openai_llm.py 首先会从配置读取密钥并初始化调用库。例如:
import openai from config import settings # 假设config.py定义了settings对象 openai.api_key = settings.OPENAI_API_KEY openai.api_base = settings.OPENAI_API_BASE # 可选,自定义API域名
这段代码将 OpenAI 的 API Key 配置给 openai 库。当需要使用非默认域名(例如代理服务或 Azure OpenAI),也可以通过
openai.api_base
来配置自定义接口地址。 -
调用 OpenAI 模型:OpenAI 提供聊天模型和补全模型等。以聊天模型为例,封装函数可能如下:
def call_openai_chat(prompt: str, model: str = "gpt-3.5-turbo", system_msg: str = "") -> str: """ 调用 OpenAI ChatCompletion 接口获取回复。 """ try: response = openai.ChatCompletion.create( model=model, messages=[ {"role": "system", "content": system_msg}, {"role": "user", "content": prompt} ] ) # 提取并返回模型回答文本 answer = response.choices[0].message["content"] return answer except Exception as e: # 记录错误并抛出异常供上层处理 logger.error(f"OpenAI API 调用失败: {e}") raise
这里使用
openai.ChatCompletion.create
接口调用指定模型,并传入对话消息列表,获取模型的回答。我们从返回的choices
中提取生成的内容。封装在函数中便于路由调用,同时做了异常捕获,出现错误时记录日志并抛出。 -
支持本地模型调用:该模块不仅限于 OpenAI。为了支持 “本地模型”等其他 LLM,项目可能采用了多态或条件来决定调用哪个后端。例如:
-
如果配置或请求指定使用本地模型,本模块会调用本地推理接口。可能通过 HTTP 请求调用本地部署的服务,或者通过 Python 方法调用本地模型库(如 Transformers)。鉴于本项目未直接依赖 Transformers 等库,推测其方案是通过开放的 API 协议调用本地服务。例如,一些本地部署的模型(如 FastChat、DeepSpeed ChatServer 等)兼容 OpenAI API,可以通过设置不同的
api_base
来使用 openai 库调用它们。这样,代码层面依然使用openai.ChatCompletion.create
,但实际请求发送到了本地服务器地址。 -
项目可能在 openai_llm.py 中为不同提供商分别定义函数,如
call_local_model(prompt)
或进一步区分例如call_chatglm(prompt)
等。如果存在 模型注册表(model_registry),则会在此维护一个字典或工厂方法,根据模型名称选择调用相应的函数,实现 统一接口,不同实现 的模式。举例来说,model_registry.py
可能定义:MODEL_DISPATCH = { "openai": call_openai_chat, "chatglm": call_chatglm_local, # ... 其他模型 }
这样在处理请求时,按请求或配置提供的模型类型,从字典中取出对应的调用函数来执行。
-
-
可扩展性:开发者可以很方便地在该模块中添加新的模型支持。例如新增一个本地模型调用,只需实现类似于
call_my_model(prompt)
的函数,并在模型注册处登记。当有新的 API Key 或连接地址时,在 config.py 中加入配置,然后在调用函数里使用即可。这种模块化设计使得整个服务可以支持多种模型而代码清晰可维护。
总结来说,openai_llm.py 抽象了底层 LLM 服务的差异,对上层(路由处理)提供统一的调用接口。不管是 OpenAI 官方模型还是本地开源模型,路由层都可以通过调用此模块函数来获取结果,而不需要关心具体调用细节。
📡 接口定义与请求格式(router/llm.py)
LLM 服务的 API 接口由 router/llm.py 定义。这里使用 FastAPI 的路由机制,将 HTTP 请求映射到我们封装的模型调用逻辑上,并定义请求/响应的数据格式。以下是该模块的核心内容:
-
定义 APIRouter:首先创建一个 FastAPI 的 APIRouter 实例,例如:
from fastapi import APIRouter, Depends from pydantic import BaseModel router = APIRouter(prefix="/llm", tags=["LLM"])
这里我们设定了路由前缀和标签,用于自动生成文档分组。之后所有在该路由下定义的接口路径都会自动加上
/llm
前缀。 -
数据模型(Pydantic):为了方便请求体和响应的数据验证,router/llm.py 内会定义 Pydantic 模型类。例如:
class LLMRequest(BaseModel): prompt: str # 用户输入的提示/问题 model: str = "openai" # 要使用的模型标识(如 "openai" 或 "chatglm") model_name: str = None # (可选)具体模型名称,比如 "gpt-3.5-turbo" class LLMResponse(BaseModel): answer: str # 模型返回的回答文本 model_used: str = None # (可选)标明使用了哪个模型
LLMRequest
定义了接口所需的请求字段:prompt
是用户输入内容,model
指定使用模型类型(默认为 OpenAI,可选填本地模型标识),model_name
则进一步指定具体模型名称(如有多个版本时使用)。LLMResponse
则描述接口返回的数据格式,这里包含模型生成的answer
,以及可能有辅助信息如实际用到的模型名等。 -
接口路由定义:使用上述模型,我们可以定义POST接口来处理LLM请求:
@router.post("/generate", response_model=LLMResponse) async def generate_text(request: LLMRequest): """ 接收用户请求,根据指定的模型返回生成结果。 """ # 从请求中获取参数 prompt = request.prompt model_type = request.model model_name = request.model_name # 根据模型类型调用对应的LLM接口 try: if model_type == "openai": result_text = call_openai_chat(prompt, model=model_name or "gpt-3.5-turbo") else: result_text = call_local_model(prompt, model=model_name) except Exception as e: # 出现错误时,抛出HTTP异常 raise HTTPException(status_code=500, detail=str(e)) return LLMResponse(answer=result_text, model_used=model_type)
上述伪代码展示了接口的大致逻辑:通过装饰器将函数绑定到
/llm/generate
POST 路径,接受LLMRequest
类型的 JSON 请求体,调用先前封装的模型函数获取结果,最后返回LLMResponse
。-
模型选择:这里根据
request.model
字段决定调用哪种模型。如果请求未指定则默认走 OpenAI;如果指定了其他类型则调用对应本地模型函数。通过这种方式,客户端可以灵活选择后端模型。例如发送{"prompt": "...", "model": "chatglm"}
将调用本地 ChatGLM 模型,而{"prompt": "...", "model": "openai", "model_name": "gpt-4"}
则调用 OpenAI 的 GPT-4 模型接口。 -
异常处理:如果调用模型出错(比如网络错误或超时),代码捕获异常并通过
HTTPException
返回 HTTP 500 错误以及错误详情,方便调用方了解失败原因。 -
限流装饰器:如前所述,如果配置了 slowapi 限流,我们可以用装饰器限制此接口的调用频率。例如:
from fastapi import Request from slowapi.decorators import limiter @router.post("/generate", response_model=LLMResponse) @limiter.limit("5/minute") async def generate_text(request: Request, body: LLMRequest): ...
这将限制每个客户端每分钟最多调用5次生成接口,多于次数将返回 429 状态码提示限流。(实际限流策略和范围可根据需求调整,例如按 IP 或按用户令牌计数)。
-
定义完成后,这个 router 会在 main.py 中被包含,最终对外形成完整的 API。利用自动生成的文档(访问 /docs
),开发者和用户可以方便地测试该接口,查看需要提供的参数和返回格式。
🔧 项目配置方式(config.py)
为了使服务灵活配置和安全地管理凭证,项目提供了 config.py 模块来集中管理配置项。通过使用 Pydantic 的 BaseSettings 或 Python 内置的配置方案,可以方便地从环境变量、.env 文件或其他来源加载配置。下面说明 config.py 的典型实现:
-
使用 BaseSettings:Pydantic 提供的
BaseSettings
类可以将环境变量映射为 Python 属性。config.py 可能定义如下:from pydantic import BaseSettings class Settings(BaseSettings): OPENAI_API_KEY: str # OpenAI API密钥 OPENAI_API_BASE: str = "https://api.openai.com/v1" # OpenAI接口基础URL,可选 DEFAULT_MODEL: str = "gpt-3.5-turbo" # 默认使用的OpenAI模型 # 本地模型配置示例 LOCAL_MODEL_API: str = None # 本地模型服务的API地址(若有) RATE_LIMIT: str = "5/minute" # 接口限流策略,例如每分钟5次 # 其他配置... class Config: env_file = ".env" # 支持从.env文件读取以上配置 case_sensitive = True # 实例化全局配置对象 settings = Settings()
如上,我们定义 Settings 类包含所需的配置项,例如 OpenAI API Key(必须提供),OpenAI API Base(默认为官方地址但可覆盖),默认模型名等,以及本地模型服务地址、限流策略等可选项。通过设置
env_file
,可以在项目根目录放置一个.env
文件写入OPENAI_API_KEY=你的Key
等环境变量,Settings 会自动加载。也可以直接在系统环境中设置这些变量,方便在不同部署环境使用不同配置。 -
读取配置:在代码中使用配置非常简单。例如在 openai_llm.py,我们通过
settings.OPENAI_API_KEY
获取 API 密钥字符串;在路由或主程序中,可以通过settings.RATE_LIMIT
获取限流设置等。这样避免了将敏感信息硬编码在代码中,也方便日后修改配置而无需改动代码。 -
配置项作用:config.py 将所有可调节的参数集中管理,包括:
- 凭据类:如 API Keys、数据库连接串等敏感信息。
- 功能开关:如是否使用某项功能的布尔值,或选择使用哪个模型提供商的选项。例如可能有
USE_OPENAI: bool
来控制默认走 OpenAI 还是本地模型。 - 模型默认值:如默认模型名称、最大上下文长度、回复最大长度等参数。
- 性能设置:如限流速率、日志级别等。
通过 config.py,开发者在部署服务时只需根据环境提供相应配置,而不用改动源码,从而遵循了配置即代码(config-as-code)的良好实践。要更换模型或密钥,只需更新配置即可生效。
📦 其他支持模块(工具函数、日志、模型注册等)
最后,我们来看项目中一些辅助性的模块,它们虽不直接处理请求,但为服务的稳定运行和可扩展性提供了支持。
-
日志模块(utils/app_logger.py):良好的日志能够帮助开发者监控服务运行状态和排查问题。项目使用 Python 内置的
logging
或第三方库(如 loguru)配置了日志。典型的日志模块可能做了如下事情:-
定义日志格式和日志等级(比如 INFO、ERROR)。
-
将日志输出到控制台以及文件,方便调试和生产追踪。
-
提供封装函数,如
setup_logger(name)
,返回带特定名称的 logger 对象,用于在各模块中记录日志。这种按模块命名的 logger 能清楚显示日志来源模块。例如,在 openai_llm.py 内使用:from utils.app_logger import setup_logger logger = setup_logger("openai_llm") logger.info("调用 OpenAI 模型: %s", model_name)
这样日志输出中会注明模块名称和时间、等级等信息,利于分析。
-