从 LangChain 演示到生产就绪的 FastAPI 后端
Source: Dev.to
为什么 LangChain 需要合适的后端架构
大多数 LangChain 示例都停留在真正的后端工作开始的地方。
许多 AI 示例存在于 notebook、脚本或 Streamlit 演示中,但一旦需要在生产后端系统中运行,它们很快就会崩溃。只要 AI 成为 API 的一部分,就必须遵循与其他后端组件相同的规则:输入输出必须明确定义,依赖需要显式声明,整体结构必须能够在不重写全部代码的情况下进行更改。
本文正是针对这一起点。
我们将建立一个干净且易于维护的 FastAPI 端点,以后端友好的方式集成 LangChain。目标是创建一个坚实的架构基础,能够一步步扩展。在此阶段,实现故意保持简洁;后续文章将逐步在此基线之上引入更高级的 LLM 和代理功能。
这里的重点 不是 展示 LangChain 的特性,而是定义一个清晰且稳健的端点架构,使其在复杂度提升时仍然易于理解、可测试且可扩展。
将 AI 视为后端组件
在查看代码之前,先统一对 AI 在后端系统中应如何处理的认识很重要。目标 不是 直接暴露 LLM,而是将 AI 逻辑封装在一个稳定且可预测的 API 背后。
一个后端就绪的 AI 端点应提供以下保证:
- 明确的请求与响应合约
- 显式的依赖编排
- 将 AI 逻辑与 HTTP 关注点分离
- 可验证且可被其他系统消费的可预测输出
FastAPI 自然契合此模型,因为它已经通过 Pydantic 模型和依赖注入强制结构。这使得在不使用特例或临时粘合代码的情况下集成 LangChain 成为可能。
使用 Pydantic 定义合约
第一个构建块是严格的 API 合约。输入和输出通过 Pydantic 模型显式定义。
# Request model
class InsightQuery(BaseModel):
question: str
context: str
# Response model
class Insight(BaseModel):
title: str
summary: str
confidence: float
@field_validator("confidence")
@classmethod
def clamp_confidence(cls, v):
"""Clamp confidence to the range [0.0, 1.0]."""
if v is None:
return 0.0
if v 1:
return 1.0
return float(v)
该合约确保无论底层 AI 逻辑如何演进,API 都保持可预测。confidence 验证器还演示了一个重要原则:即使 AI 产生不完美的数值,后端也会在返回响应前强制一致性。若没有此类校验,LLM 输出很快会变得不可预测,难以在真实系统中集成。
通过 FastAPI Depends 注入 LLM
不要在端点(或链)内部直接创建 LLM,而是使用 FastAPI 的依赖注入进行注入。
# FastAPI endpoint definition
@router.post(path="/query", response_model=Insight)
def create_insight(
request: InsightQuery,
settings: Settings = Depends(get_settings),
llm: BaseChatModel = Depends(init_openai_chat_model),
):
...
语言模型本身在单独的依赖函数中初始化。
def init_openai_chat_model(settings: Settings = Depends(get_settings)):
"""
Initializes and returns the LangChain OpenAI chat model.
"""
return ChatOpenAI(
model=settings.openai_model.model_name,
temperature=settings.openai_model.temperature,
api_key=settings.openai_model.api_key,
)
优势
- 端点专注于编排。
- 配置集中管理。
- 在测试时可以轻松替换或 mock LLM。
从 FastAPI 的角度来看,语言模型只是另一个依赖。
Source: …
封装 LangChain 逻辑
LangChain 的逻辑本身被封装在一个专用函数中。端点不需要了解链是如何构建或执行的。
def run_insight_chain(
prompt_messages: ChatModelPrompt,
llm: BaseChatModel,
question: str,
context: str,
) -> Insight:
"""
Builds and runs the LangChain insight chain.
"""
prompt_template = ChatPromptTemplate([
("system", prompt_messages.system),
("human", prompt_messages.human),
])
parser = PydanticOutputParser(pydantic_object=Insight)
chain = prompt_template | llm | parser
response = chain.invoke({
"format_instruction": parser.get_format_instructions(),
"question": question,
"context": context,
})
return response
这种设计将关注点清晰地分离。提示构建、模型执行和输出解析都集中在同一个位置。其余的应用只需要处理输入和输出。
在 FastAPI 端点中编排一切
端点现在成为一个轻量的编排层。
@router.post(path="/query", response_model=Insight)
def create_insight(
request: InsightQuery,
settings: Settings = Depends(get_settings),
llm: BaseChatModel = Depends(init_openai_chat_model),
):
"""
POST /query – Creates a new insight for a given context and related question.
"""
prompt_messages = load_prompt_messages(
settings.prompt.insight_path,
settings.prompt.insight_version,
)
response = run_insight_chain(
prompt_messages,
llm,
request.question,
request.context,
)
return response
该端点现在:
- 加载相应的提示模板。
- 调用封装好的 LangChain 函数。
- 返回经过验证的
Insight响应。
小结
- 将 AI 视为后端组件:稳定的契约、显式的依赖和封装的逻辑。
- 使用 Pydantic 实现严格的请求/响应模型。
- 利用 FastAPI 的依赖注入 来管理 LLM 实例。
- 将 LangChain 链封装 为可复用函数。
有了这些基础,你可以自信地添加更复杂的 LLM 功能、代理编排、缓存、监控和测试,而无需重写核心端点逻辑。
# Fullscreen Mode
The endpoint coordinates configuration, prompt loading, and chain execution without embedding business logic. This keeps the API readable and makes future extensions straightforward.
为什么这种结构可扩展
即使示例很简单,结构也有意向前兼容。
- 检索以后可以作为另一个依赖添加。
- 代理逻辑可以替换链函数,而无需触碰端点契约。
- 状态处理和错误管理可以叠加在上层,而无需重写核心流程。
最重要的是,AI 被视为后端关注点,而不是特例。它遵循与生产系统中其他组件相同的架构规则。
最后思考
本文展示了在 AI 实验与将其作为后端系统的一部分进行运营之间的区别。它奠定了面向生产的 AI 后端的第一块基石。从此,加入检索、记忆或代理等功能就成为一种架构决策,而不再是重构。
💻 GitHub 代码:
hamluk/fastapi-ai-backend/part-2