最近有不少读者在后台问我同一类问题:大模型应用开发到底要学哪些东西?网上资料要么是单点讲一个 API 调用,要么是某个框架的广告软文,学完还是拼不出一个完整项目。
这次整理一份“AI 大模型应用开发全家桶”实战教程,把 LLM、RAG、Agent、MCP、工作流、工具调用这些高频概念一次性串起来,并给出 FastAPI 与 SpringBoot 双版本实现思路。无论你是 Python 技术栈还是 Java 技术栈,都能找到可以直接落地的参考方案。
1. 背景与核心概念
1.1 大模型应用开发的现状
过去两年,大模型应用开发已经从“调用一次 ChatGPT API 返回文本”进化成了一门完整的工程学科。现在的真实项目里,模型不仅要会聊天,还要能读文档、查数据库、调第三方接口、按固定流程执行任务,甚至多个模型协作完成复杂业务。
举个例子:一个企业知识库问答系统,如果只把用户问题原样丢给大模型,模型会胡说八道。正确的做法是先检索公司内部文档,把相关内容拼进提示词,再让模型基于这些材料回答。这个过程中涉及文档加载、文本切分、向量化、相似度检索、提示词组装,远不是一个 client.chat.completions.create() 能搞定的。
因此,想要系统掌握大模型应用开发,至少需要理解六个关键概念:LLM、RAG、Agent、MCP、工作流、工具调用。它们互相独立,又经常组合出现在同一个项目里。
1.2 六个核心概念通俗解释
LLM(大语言模型) 是整个体系的大脑。它是接受文本输入、生成文本输出的模型,比如 OpenAI 的 GPT 系列、开源的 Qwen、GLM 等。在应用开发中,我们通常不直接训练模型,而是通过 HTTP API 调用模型服务,并在提示词层面做文章。
RAG(检索增强生成) 是为了解决模型“不知道私有数据”的问题。思路是先建一个知识库,把文档切成碎片、做向量化存储;用户提问时,先从知识库检索相关片段,拼进提示词,再让模型回答。这样回答有据可依,还能随时更新知识库内容,不需要重新训练模型。
Agent(智能体) 是让模型具备“自主行动”能力的封装。它不再只是回答一句话,而是根据目标自主规划步骤、调用工具、观察结果、调整策略,直到完成任务。常见形态有 ReAct 模式、Plan-and-Execute 模式。
MCP(Model Context Protocol,模型上下文协议) 是 Anthropic 提出的开放协议,目的是统一模型与外部工具之间的通信方式。简单理解,它像 USB-C 接口一样,让模型可以通过标准协议连接不同的数据源和工具服务,避免每接一个工具就写一套私有集成代码。
工作流(Workflow) 是把一组确定性的步骤编排起来,让模型或普通代码按顺序执行。例如“先判断问题类型,再选择对应处理流程,最后生成回复”。工作流适合流程固定、步骤清晰的业务场景,与 Agent 的自主决策形成互补。
工具调用(Function Calling / Tool Calling) 是模型输出结构化指令的能力。开发者预先定义一批函数(如查天气、查订单),模型在生成回复时如果判断需要调用某个函数,就输出函数名和参数,由我们的代码执行并返回结果给模型。这样模型就能感知实时数据。
1.3 为什么需要 FastAPI 和 SpringBoot 双版本
国内很多团队的现状是:AI 算法小组用 Python,业务后端用 Java。如果只写一种技术栈的教程,另一半团队搭建起来总是隔靴搔痒。
FastAPI 是 Python 生态中非常适合做 AI 应用服务的框架,异步性能好,类型提示完善,跟 OpenAPI 天然集成。而 SpringBoot 在 Java 体系中依然是企业级应用的事实标准,稳定性、生态成熟度、团队熟悉度都很高。
双版本并不意味着一份逻辑写两遍,而是把同一套 AI 应用的架构设计,映射到两个技术栈上。你只要理解清楚模块划分和调用链,换成任何一种语言都能快速复刻。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体架构与设计思路
2.1 一套能力,两种技术栈
做双版本经验后我最大的体会是:不要上来就写代码,先把架构图画清楚。AI 应用的核心分层并不多:
| 层级 | 职责 | 技术要素 |
|---|---|---|
| 接入层 | 对外提供 HTTP 接口 | FastAPI 路由 / SpringBoot Controller |
| 编排层 | 负责对话、检索、工具调用的流程控制 | LLM 调用、Prompt 组装、循环逻辑 |
| 工具层 | 封装模型需要调用的外部能力 | HTTP 调用、数据库查询、文件读写 |
| 存储层 | 保存知识库、会话记录、向量数据 | 向量库、Redis、MySQL |
任何复杂项目都能拆成这四层。新人最容易犯的错误是,所有逻辑全堆在路由函数里,维护两三个月之后自己都看不懂。有了分层,后续加 RAG、加工具、换模型服务,都只是替换对应层的实现。
2.2 双版本模块划分对比
FastAPI 版本建议按功能目录组织:
code复制ai-app-python/
├── app/
│ ├── main.py # FastAPI入口
│ ├── config.py # 配置读取
│ ├── routers/
│ │ ├── chat.py # 对话接口
│ │ └── agent.py # Agent接口
│ ├── core/
│ │ ├── llm.py # LLM调用封装
│ │ ├── rag.py # RAG流程
│ │ └── tools.py # 工具调用注册
│ └── schemas/
│ └── chat.py # 请求响应模型
├── requirements.txt
└── .env
SpringBoot 版本对应结构:
code复制ai-app-java/
├── pom.xml
├── src/main/java/com/example/aiapp/
│ ├── AiAppApplication.java
│ ├── controller/
│ │ ├── ChatController.java
│ │ └── AgentController.java
│ ├── service/
│ │ ├── LLMService.java
│ │ ├── RAGService.java
│ │ └── AgentService.java
│ ├── tool/
│ │ └── ToolRegistry.java
│ └── config/
│ └── LLMConfig.java
└── src/main/resources/
└── application.yml
熟悉这个目录结构后,后面的代码都按这个骨架来填。
2.3 关键技术选型
LLM 调用层面,Python 侧常用 openai SDK(兼容 OpenAI 协议的服务都能用),Java 侧可以用 Spring Web 的 WebClient 或 RestTemplate 直接请求模型 HTTP 接口,也可以引入对应厂商 SDK。
向量存储层面,入门阶段不建议直接上大型分布式向量库。先用内存列表或轻量级库把 RAG 流程跑通,理解原理后再迁移到正式环境。这样排查问题时不会被基础设施干扰。
3. 环境准备与版本说明
3.1 Python / FastAPI 环境
需要安装 Python 3.10 或更高版本,版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。建议使用虚拟环境隔离依赖。
核心依赖如下,可以在 requirements.txt 中声明:
txt复制fastapi>=0.110
uvicorn>=0.29
openai>=1.30
pydantic>=2.6
python-dotenv>=1.0
httpx>=0.27
安装命令:
bash复制python -m venv venv
source venv/bin/activate # Windows 使用 venv\Scripts\activate
pip install -r requirements.txt
3.2 Java / SpringBoot 环境
需要 JDK 17 及以上版本。SpringBoot 版本推荐 3.x,因为 3.x 基于 Jakarta EE,并且对响应式编程支持更完整。Maven 或 Gradle 都可以,本文以 Maven 为例。
关键依赖:
xml复制<!-- pom.xml 核心依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.x</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
注意:具体版本号请以 Maven 中央仓库实际发布为准,文章示例重在思路。
3.3 模型服务准备
无论用哪家模型,都需要准备:
- API Key
- Base URL(模型服务地址)
- 模型名称(如
gpt-4o-mini、qwen-plus、glm-4-plus等)
如果使用 OpenAI 兼容协议的本地服务(如 vLLM、Ollama),只需把 Base URL 指向本地地址即可,代码里的调用逻辑完全一样。
4. FastAPI 版本实战
4.1 配置与 LLM 调用封装
先在 .env 文件里保存敏感配置:
env复制OPENAI_API_KEY=sk-xxxx
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-mini
然后编写配置读取模块 app/config.py:
python复制# 文件路径:app/config.py
import os
from dotenv import load_dotenv
load_dotenv()
class Settings:
OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY", "")
OPENAI_BASE_URL: str = os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
OPENAI_MODEL: str = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
settings = Settings()
接着封装 LLM 调用,app/core/llm.py:
python复制# 文件路径:app/core/llm.py
from openai import AsyncOpenAI
from app.config import settings
client = AsyncOpenAI(
api_key=settings.OPENAI_API_KEY,
base_url=settings.OPENAI_BASE_URL,
)
async def chat_completion(messages: list[dict], tools: list = None):
kwargs = {
"model": settings.OPENAI_MODEL,
"messages": messages,
}
if tools:
kwargs["tools"] = tools
response = await client.chat.completions.create(**kwargs)
return response.choices[0].message
这里的关键点是 base_url 可配置,意味着你可以把请求发送到任意 OpenAI 兼容模型服务,本地私有化部署也一样。后续更换模型厂商时,只需要修改 .env,业务代码不用动。
4.2 实现 LLM 基础对话接口
在 app/routers/chat.py 里定义 /api/chat 接口:
python复制# 文件路径:app/routers/chat.py
from fastapi import APIRouter
from pydantic import BaseModel
from app.core.llm import chat_completion
router = APIRouter(prefix="/api", tags=["chat"])
class ChatRequest(BaseModel):
message: str
history: list[dict] = []
class ChatResponse(BaseModel):
reply: str
@router.post("/chat", response_model=ChatResponse)
async def chat(req: ChatRequest):
messages = req.history + [{"role": "user", "content": req.message}]
result = await chat_completion(messages)
return ChatResponse(reply=result.content or "")
启动服务:
bash复制uvicorn app.main:app --reload --port 8000
请求示例:
bash复制curl -X POST http://localhost:8000/api/chat \
-H "Content-Type: application/json" \
-d '{"message": "介绍一下FastAPI的优势"}'
这里的 history 字段用来维护多轮对话上下文,前端可以将历史消息传给后端。需要注意,对话历史会消耗 Token,长对话场景要考虑截断或摘要压缩。
4.3 工具调用的核心逻辑
工具调用是 Agent 的基础。先定义工具函数,app/core/tools.py:
python复制# 文件路径:app/core/tools.py
async def get_weather(city: str) -> str:
"""模拟天气查询工具"""
# 实际项目中这里可以请求天气API
return f"{city}的天气是晴朗,温度22°C"
TOOL_REGISTRY = {
"get_weather": {
"name": "get_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
},
"func": get_weather,
}
}
然后修改对话接口,让模型在必要时返回工具调用指令:
python复制# 文件路径:app/routers/chat.py(新增工具调用路由)
from app.core.tools import TOOL_REGISTRY
@router.post("/chat_with_tool", response_model=ChatResponse)
async def chat_with_tool(req: ChatRequest):
messages = req.history + [{"role": "user", "content": req.message}]
tools = [
{k: v for k, v in tool.items() if k != "func"}
for tool in TOOL_REGISTRY.values()
]
result = await chat_completion(messages, tools=tools)
if result.tool_calls:
# 模型请求调用工具,我们执行并返回结果
for call in result.tool_calls:
func_name = call.function.name
arguments = json.loads(call.function.arguments)
tool_func = TOOL_REGISTRY[func_name]["func"]
tool_result = await tool_func(**arguments)
messages.append({
"role": "assistant",
"content": result.content,
"tool_calls": [call.model_dump()],
})
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": tool_result,
})
final = await chat_completion(messages, tools=tools)
return ChatResponse(reply=final.content or "")
return ChatResponse(reply=result.content or "")
这段代码体现了工具调用的完整闭环:模型输出结构化调用请求 → 程序执行本地函数 → 把结果以 tool 角色回传给模型 → 模型基于结果生成最终回复。理解这个循环,就理解了大部分 Function Calling 框架的底层原理。
4.4 组装一个轻量 RAG 流程
RAG 的思路可以拆成两步:建索引和检索增强。本文先用轻量内存版演示流程,避免引入外部向量库导致新手环境配置困难。
app/core/rag.py:
python复制# 文件路径:app/core/rag.py
import hashlib
import numpy as np
from app.core.llm import client
from app.config import settings
class MemoryVectorStore:
def __init__(self):
self.documents = []
self.embeddings = []
async def add_document(self, text: str):
# 调用模型服务生成向量,这里使用 text-embedding 类模型
resp = await client.embeddings.create(
model="text-embedding-3-small",
input=text
)
self.documents.append(text)
self.embeddings.append(np.array(resp.data[0].embedding))
async def search(self, query: str, top_k: int = 3):
resp = await client.embeddings.create(
model="text-embedding-3-small",
input=query
)
query_vec = np.array(resp.data[0].embedding)
scores = [
np.dot(query_vec, emb) / (np.linalg.norm(query_vec) * np.linalg.norm(emb) + 1e-9)
for emb in self.embeddings
]
indices = np.argsort(scores)[::-1][:top_k]
return [self.documents[i] for i in indices]
vector_store = MemoryVectorStore()
async def rag_answer(question: str) -> str:
docs = await vector_store.search(question)
context = "\n\n".join(docs)
messages = [
{
"role": "system",
"content": "你是文档问答助手,只能基于提供的资料回答,资料中没有的信息要明确说不知道。",
},
{
"role": "user",
"content": f"资料:\n{context}\n\n问题:{question}",
},
]
result = await chat_completion(messages)
return result.content or ""
生产环境建议把 MemoryVectorStore 替换为 FAISS、ChromaDB、Milvus 或 pgvector。但替换前后的 RAG 主流程不会变:文档入库 → 向量化 → 相似度检索 → 构造提示词 → 生成回答。
4.5 实现一个极简 Agent
有了工具调用能力,Agent 就是一个带有循环和停止条件的执行器。这里实现一个简单的 ReAct 风格 Agent:
python复制# 文件路径:app/core/agent.py
import json
from app.core.llm import chat_completion
from app.core.tools import TOOL_REGISTRY
async def run_agent(user_input: str, max_iterations: int = 5) -> str:
messages = [
{"role": "system", "content": "你是一个智能助手,可以调用工具完成任务,每次只输出一个工具调用或最终回答。"},
{"role": "user", "content": user_input},
]
tools = [
{k: v for k, v in tool.items() if k != "func"}
for tool in TOOL_REGISTRY.values()
]
for _ in range(max_iterations):
result = await chat_completion(messages, tools=tools)
if result.tool_calls:
messages.append({
"role": "assistant",
"content": result.content,
"tool_calls": [call.model_dump() for call in result.tool_calls],
})
for call in result.tool_calls:
func_name = call.function.name
arguments = json.loads(call.function.arguments)
tool_result = await TOOL_REGISTRY[func_name]["func"](**arguments)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": str(tool_result),
})
continue
return result.content or ""
return "已达最大迭代次数,任务未能完成"
max_iterations 是 Agent 的安全阀。缺少这个限制,模型可能陷入“调用工具→观察结果→再调用”的死循环,既消耗 Token 又延迟响应。工程上还要加超时、预算、人工确认等机制。
4.6 MCP 与实践建议
MCP 的引入让工具接入方式标准化。FastAPI 项目中,可以写一个 MCP Client 模块,负责连接 MCP Server 并动态发现工具列表。核心思路是:
- MCP Server 通过配置文件暴露工具能力。
- 客户端启动时建立连接,拉取工具 Schema。
- 把 Schema 传给模型作为
tools参数。 - 模型请求调用工具时,把参数转发给 MCP Server 执行。
相比直接写死 TOOL_REGISTRY,MCP 的收益是工具可以被不同 Agent、不同项目复用,只要对方也支持 MCP 协议。但 MCP 还在快速演进,生产环境要关注协议版本兼容性,不要盲目追新。
5. SpringBoot 版本实战
5.1 配置 LLM 客户端
在 application.yml 中配置模型服务参数:
yaml复制# 文件路径:src/main/resources/application.yml
spring:
application:
name: ai-app
llm:
api-key: ${OPENAI_API_KEY:sk-xxxx}
base-url: ${OPENAI_BASE_URL:https://api.openai.com/v1}
model: ${OPENAI_MODEL:gpt-4o-mini}
编写配置属性类:
java复制// 文件路径:src/main/java/com/example/aiapp/config/LLMProperties.java
package com.example.aiapp.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "llm")
public class LLMProperties {
private String apiKey;
private String baseUrl;
private String model;
}
5.2 实现 LLM 调用服务
使用 WebClient 发起请求:
java复制// 文件路径:src/main/java/com/example/aiapp/service/LLMService.java
package com.example.aiapp.service;
import com.example.aiapp.config.LLMProperties;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Map;
@Service
public class LLMService {
private final WebClient webClient;
private final LLMProperties properties;
private final ObjectMapper objectMapper;
public LLMService(LLMProperties properties, ObjectMapper objectMapper) {
this.properties = properties;
this.objectMapper = objectMapper;
this.webClient = WebClient.builder()
.baseUrl(properties.getBaseUrl())
.defaultHeader("Authorization", "Bearer " + properties.getApiKey())
.build();
}
public Mono<JsonNode> chatCompletion(List<Map<String, String>> messages, JsonNode tools) {
ObjectNode requestBody = objectMapper.createObjectNode();
requestBody.put("model", properties.getModel());
ArrayNode messagesNode = requestBody.putArray("messages");
messages.forEach(msg -> {
ObjectNode messageNode = objectMapper.createObjectNode();
messageNode.put("role", msg.get("role"));
messageNode.put("content", msg.get("content"));
messagesNode.add(messageNode);
});
if (tools != null) {
requestBody.set("tools", tools);
}
return webClient.post()
.uri("/chat/completions")
.bodyValue(requestBody)
.retrieve()
.bodyToMono(JsonNode.class);
}
}
WebClient 依赖 spring-boot-starter-webflux,如果只想用 MVC 同步风格,改用 RestTemplate 也是同理。重点在于请求体的组织方式要和 OpenAI 协议保持一致。
5.3 实现对话接口和工具调用
定义请求与响应 DTO:
java复制// 文件路径:src/main/java/com/example/aiapp/controller/ChatController.java
package com.example.aiapp.controller;
import com.example.aiapp.service.LLMService;
import com.example.aiapp.tool.ToolRegistry;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class ChatController {
private final LLMService llmService;
private final ToolRegistry toolRegistry;
private final ObjectMapper objectMapper;
public ChatController(LLMService llmService, ToolRegistry toolRegistry, ObjectMapper objectMapper) {
this.llmService = llmService;
this.toolRegistry = toolRegistry;
this.objectMapper = objectMapper;
}
@PostMapping("/chat")
public Mono<JsonNode> chat(@RequestBody Map<String, Object> req) {
String message = (String) req.get("message");
List<Map<String, String>> messages = new ArrayList<>();
messages.add(Map.of("role", "user", "content", message));
return llmService.chatCompletion(messages, null)
.map(response -> response.at("/choices/0/message/content"));
}
@PostMapping("/chat_with_tool")
public Mono<JsonNode> chatWithTool(@RequestBody Map<String, Object> req) {
String message = (String) req.get("message");
List<Map<String, String>> messages = new ArrayList<>();
messages.add(Map.of("role", "user", "content", message));
JsonNode tools = toolRegistry.getToolsSchema();
return llmService.chatCompletion(messages, tools)
.flatMap(response -> {
JsonNode toolCalls = response.at("/choices/0/message/tool_calls");
if (toolCalls.isArray() && toolCalls.size() > 0) {
// 执行工具调用并继续对话
return handleToolCalls(messages, response, toolCalls);
}
return Mono.just(response.at("/choices/0/message/content"));
});
}
private Mono<JsonNode> handleToolCalls(List<Map<String, String>> messages,
JsonNode response,
JsonNode toolCalls) {
// 实现思路:遍历 toolCalls,执行本地注册函数,把结果追加到 messages,
// 然后再次调用 llmService.chatCompletion(messages, tools)
return Mono.just(response.at("/choices/0/message/content"));
}
}
工具注册表的思路和 Python 版本一致,通过 Map 维护工具名与执行函数的映射,这里不展开每个实现类。
5.4 配置工具注册表
java复制// 文件路径:src/main/java/com/example/aiapp/tool/ToolRegistry.java
package com.example.aiapp.tool;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.stereotype.Component;
import java.util.function.Function;
@Component
public class ToolRegistry {
private final ObjectMapper objectMapper;
public ToolRegistry(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
public JsonNode getToolsSchema() {
ArrayNode tools = objectMapper.createArrayNode();
ObjectNode weatherTool = objectMapper.createObjectNode();
weatherTool.put("type", "function");
ObjectNode function = weatherTool.putObject("function");
function.put("name", "get_weather");
function.put("description", "查询指定城市的天气");
ObjectNode parameters = function.putObject("parameters");
parameters.put("type", "object");
ObjectNode properties = parameters.putObject("properties");
ObjectNode cityProp = properties.putObject("city");
cityProp.put("type", "string");
cityProp.put("description", "城市名称");
parameters.putArray("required").add("city");
tools.add(weatherTool);
return tools;
}
}
5.5 RAG 服务与 Agent
Java 侧 RAG 与 Python 思路一致。如果要快速验证流程,可以先用内存列表保存文档和向量,后续替换为正式向量库。
java复制// 文件路径:src/main/java/com/example/aiapp/service/RAGService.java
package com.example.aiapp.service;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class RAGService {
private final List<String> documents = new ArrayList<>();
private final LLMService llmService;
public RAGService(LLMService llmService) {
this.llmService = llmService;
}
public void addDocument(String text) {
documents.add(text);
}
// 检索和回答逻辑需要结合向量化能力,
// 思路是:先生成 query 向量,再与文档向量计算相似度,
// 选出 top_k 文档后拼接 Prompt 交给模型。
}
真实项目里,建议用 Spring AI 或 LangChain4j 这类框架来降低样板代码量。但这些框架发展很快,选型前先确认团队的技术储备和维护能力。
Service 层实现 Agent 时,本质上就是在 Java 里写一个带最大迭代次数的循环。模型返回工具调用则执行工具,否则返回最终回答。这个循环与语言无关,核心思路完全一致。
6. 双版本对比与选型建议
6.1 开发效率对比
FastAPI 版本的代码量通常更少。Python 生态里 openai SDK 非常成熟,类型提示支持好,配合 FastAPI 的自动文档,几分钟就能把接口跑起来。对于 AI 原型验证、算法团队内部工具、数据密集型应用,FastAPI 是首选。
SpringBoot 版本的优势在于工程规范性。Java 强类型语言在大型团队协作时更容易做接口约束、代码审查和自动化测试。如果团队后端主力是 Java,用 SpringBoot 维护长期项目会更顺手,也更容易与企业现有的开发规范融合。
6.2 团队技术栈与部署条件
选型时还要考虑部署条件。两者都可以容器化部署,但如果公司已有成熟的 K8s 和 Java 微服务治理体系,SpringBoot 版本接入成本更低。Python 版本在 GPU 服务、模型推理集成方面天然更近,如果你的应用需要连接 Python 生态的机器学习库,选 FastAPI 更合适。
7. 常见问题与排查思路
7.1 模型调用超时
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 请求长时间无响应,最终报 timeout | 网络环境问题;模型服务负载过高;请求 Token 量过大 | 先缩短提示词和后文长度测试;检查 Base URL 连通性;合理设置超时时间,比如 60 秒;生产环境一定要配置重试与熔断 |
7.2 上下文长度超限
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 报错提示 token 超过模型最大上下文 | 多轮对话历史不停累积;RAG 拼接资料过多 | 按 token 数截断历史消息;RAG 检索片段控制在 3-5 段;必要时用摘要压缩早期对话 |
7.3 RAG 检索效果差
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 回答内容与知识库资料对不上 | 文本切分粒度不合适;向量模型与文档领域不匹配;没有做重排序 | 调整切分块大小(一般 200-500 字);尝试不同的 embedding 模型;检索后加 rerank 环节,把语义最相关的结果排到前面 |
7.4 Agent 循环失控
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| Agent 不断调用工具,Token 消耗巨大 | 缺少最大迭代次数限制;提示词没有约束输出格式;工具返回结果不明确 | 设置硬性循环上限;在 system prompt 中明确“要么调用工具,要么直接回答”;工具返回结果加上成功状态和简洁信息 |
7.5 MCP 连接失败
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 无法发现 MCP Server 上的工具 | Server 地址配置错误;协议版本不匹配;鉴权失败 | 先用 MCP 官方调试工具单独测试连接;检查 Server 的启动参数和配置文件;确认 SDK 版本与协议版本兼容 |
8. 最佳实践与工程建议
8.1 配置管理与环境隔离
API Key、Base URL、模型名称这类信息绝不能硬编码在代码里。开发环境用 .env 或本机环境变量,测试和生成环境用配置中心或 CI/CD 注入。不同环境之间建议启用不同的模型实例,避免测试流量污染线上数据。
8.2 提示词工程规范化
提示词是 AI 应用的核心资产。建议统一管理,不要散落在业务代码字符串里。可以建立 prompts/ 目录,用文件维护 system prompt 与模板;模板中使用明确的占位符,方便测试不同版本。每次修改提示词要记录变更原因,必要时做 A/B 对比评估。
8.3 成本与性能控制
大模型接口调用成本比较高,需要重复的请求建议加缓存。RAG 场景中,相同或相似的问题可以使用语义缓存,命中后直接返回历史答案。流式输出(SSE)能明显改善用户体验,FastAPI 可以用 StreamingResponse,SpringBoot 可以用 SseEmitter 或 WebFlux 的 Flux。同时要监控 Token 消耗,按用户、按接口、按角色做成本统计。
8.4 安全与最小权限
对外提供 AI 接口时,必须做用户鉴权,不能裸奔到公网。工具调用的权限设计遵循最小权限原则:Agent 具备的能力只限当前业务需要,不暴露数据库全局读写、文件删除、生产环境变更等高风险操作。涉及用户隐私的文档检索,要做数据权限过滤,不能让普通用户检索到越权内容。
8.5 可观测性是必备能力
AI 应用和普通接口不一样,同一个问题可能每次结果都不同。生产环境必须记录完整调用链路:用户输入、检索命中的文档、最终拼接的提示词、模型输出、Token 消耗、延迟。排查线上问题时,这些日志是还原现场的唯一线索。建议给每次请求分配一个 trace_id,贯穿所有日志和下游调用。
9. 总结与下一步学习建议
这篇教程围绕六个核心概念展开:LLM 是基础模型能力,RAG 解决知识外挂问题,工具调用让模型具备实时交互能力,Agent 把工具调用扩展成自主闭环,工作流用于固定流程编排,MCP 则是工具接入的标准化协议。双版本的实现展示了一个重要事实:架构设计是通用的,语言只是载体。
下一步的实践路径建议这样走:先跑通本文的基础对话接口,再逐步加入工具调用,然后用内存版 RAG 理解检索流程,最后把 Agent 循环封装成独立服务。每一步都亲手验证,再进入生产级优化:向量库选型、流式响应、缓存、日志链路、权限控制。如果你想深入 MCP,可以去官方文档翻一遍协议规范,自己写一个简单的 MCP Server 发布到本地验证工具发现流程。
不要指望一口气掌握全部内容,AI 应用开发本身就是不断组合新技术的过程。把基础模块吃透了,后面学习 LangChain、Spring AI、LangChain4j 这些框架时会轻松很多。码字不易,如果这篇对你有帮助,可以先收藏备用,后续我会继续出更多 AI 应用开发实战内容。
