如果你最近在学 AI 应用开发,多半会有一个共同感受:LLM 就是调接口,RAG 就是向量检索加提示词,Agent 就是循环调用,MCP 就是一个工具协议。单看每个概念都不算难,可真要动手做一个完整项目时,这些知识立刻会变成碎片——模型应该在什么环节接入知识库?工具调用由谁触发?Agent 到底什么时候该停下来?工作流应该用代码编排还是平台编排?FastAPI 写通了一套,换到 Java 团队又得重头再来。
这套覆盖 LLM、RAG、Agent、MCP、工作流、工具调用 的 AI 大模型应用实战全家桶,值得关注的核心原因,是它补上了从“会调模型 API”到“会做 AI 应用工程”之间那段空白。它不是某一个功能的单点教程,而是把 AI 应用最常见的几层能力按一条链路串起来,并且同时给出 FastAPI 和 SpringBoot 双版本。这意味着无论你平时用 Python 还是 Java,都能找到可以对照落地的实现路径。
本文将先讲清楚全家桶里每一层技术到底解决什么问题,再拆解技术选型和架构设计,然后分别给出 LLM、工具调用、RAG、Agent、MCP、工作流的代码示例,最后补充排查思路和工程化建议。读完之后,你至少能判断:自己的项目应该使用全家桶里的哪些层,以及从哪套技术栈切入最合适。
1. 为什么需要一套 AI 大模型应用全家桶
1.1 AI 应用开发真正的门槛不在“调 API”
很多开发者是从“调用模型接口”开始接触 AI 的。写一个 chat.completions.create,传一段 messages,拿到返回内容,会觉得大模型应用不过如此。
但真实业务远不是这么简单。一个稍微像样的 AI 应用,通常要同时处理几个问题:知识从哪里来、怎么保证回答不胡说、模型需要调用什么工具、多步骤任务如何编排、外部工具以什么方式接入、不同团队的技术栈怎么统一。这些问题单独研究都有答案,组合在一起时,大部分人都会卡在“不知道该先做哪一层,也不知道每一层的边界在哪里”。
这套全家桶解决的是认知和路径问题:它先把每一层能力拆开,再用统一的应用把它们串起来。你不是在学某个模型的具体用法,而是在学一套“AI 应用分层搭建”的通用方法。
1.2 “会用”和“会做工程”是两码事
从网络课程和开源项目里看到的 AI 示例,绝大多数是单点 Demo:技能就调一次模型,RAG 就检索一次向量库,Agent 就循环调用一次工具。而真实项目关心的是可维护性、可观测性、成本控制、权限边界、版本兼容。同一个功能,Demo 里跑通只需 20 行代码,拿到生产环境就要考虑超时、重试、日志、Token 统计、用户鉴权、模型切换等一堆问题。
所以这篇文章给出的判断是:这套全家桶的真正价值不是“全”,而是链路。从对话到知识增强,从知识增强到自主决策,从自主决策到工具执行,再到多步骤编排,它帮你建立的是一个端到端的 AI 应用骨架。有了这个骨架,后续换模型、换向量库、加工具,都是替换局部模块,而不是推翻重写。
1.3 哪些读者最适合从中受益
- 从后端转 AI 应用开发的工程师:FastAPI 和 SpringBoot 双版本正好覆盖 Python 和 Java 两条路线;
- 需要快速搭建内部 AI 系统的技术负责人:可以用这套架构做技术选型参考;
- 正在准备 AI 应用方向面试的开发者:把 LLM、RAG、Agent、MCP 串起来讲清楚,比背零散概念更有说服力;
- 产品和技术协作紧密的团队:理解了工作流和 Agent 的分工后,更容易和业务方讨论“哪些需求适合自动化”。
如果你是在研究大模型底层训练、微调或推理优化的,这套全家桶对你不适用,它定位在“模型之上、业务之下”的应用层。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心概念梳理:全家桶里的每一层到底在解决什么
很多教程会把 LLM、RAG、Agent、MCP、工作流混着讲,越讲越模糊。其实只要抓住每个概念对应的核心问题,边界就很清楚了。
2.1 LLM:应用的“大脑底座”
LLM 是这一切的基础。它负责理解用户输入、生成回答、拆解任务、判断调用什么工具。没有 LLM,后面所有层都失去智能能力。在实际工程中,关注点不是模型结构,而是模型能力边界、上下文长度、Token 成本、响应速度和是否支持工具调用。
2.2 RAG:解决知识和时效问题
LLM 的问题是知识有截止日期,也无法直接访问你的私有文档。RAG(Retrieval-Augmented Generation,检索增强生成)的思路是:先把文档切块并向量化存入向量库,用户提问时先把问题向量化,去库里检索最相关的内容,再把检索结果拼进 Prompt 交给模型生成回答。
它解决的核心问题是“模型不知道的知识,用外部材料补充进来”。这也是企业内部知识库问答最主流的方案。
2.3 Agent:从“回答问题”到“完成任务”
LLM 的默认行为是“你说一句,我回一句”。但很多任务需要多步推理:查数据、做判断、调接口、再根据结果继续推理。Agent 就是让模型不再只做一轮问答,而是拥有一个不断循环的“思考-行动-观察”过程,直到完成任务或达到最大步数。
它对工程的要求主要是循环控制、工具结果回传、终止条件和防死循环。
2.4 MCP:工具接入的标准化协议
MCP(Model Context Protocol,模型上下文协议)要解决的是工具接入的碎片化问题。以前每个 Agent 框架接一个外部工具,都要写一套自定义协议;MCP 提供了一套统一标准,让模型应用以标准方式发现和调用外部工具。
类比来说,如果说 USB-C 统一了硬件接口,MCP 就是 AI 世界的“统一接口标准”。它之所以在近一年快速流行,正是因为各家的 Agent 框架都意识到:工具生态如果不能互通,Agent 规模化就很难。
2.5 工作流:让过程可控
Agent 适合不确定路径的任务,但有些业务不允许模型自由发挥。例如工单处理、审批流、多步数据清洗,这些步骤是确定性的,需要稳定执行。工作流就是把步骤显式编排起来:先做什么,后做什么,哪个步骤失败就终止或重试。
它和 Agent 的区别可以简化成一句话:Agent 由模型决定下一步,工作流由开发者决定下一步。实际项目中经常是两者结合。
2.6 工具调用:Agent 的“双手”
工具调用(Function Calling / Tool Calling)是模型输出的一种结构化指令:模型不直接执行操作,而是输出“我要调用某某函数,参数是什么”,由应用代码去真正执行。这是 Agent 能操作数据库、查天气、发消息、改订单状态的基础。
下表可以快速帮助你理解每一层:
| 能力层 | 核心问题 | 典型场景 | 落地形态 |
|---|---|---|---|
| LLM | 怎么理解与生成 | 对话、总结、翻译 | 模型 API + Prompt 工程 |
| RAG | 知识从哪里来 | 客服、私有文档问答 | 向量库 + 检索 + 生成 |
| Agent | 目标怎么完成 | 自动化分析、运维操作 | 规划 + 工具调用循环 |
| MCP | 工具怎么接入 | 连接数据库、浏览器、业务系统 | 统一工具协议 |
| 工作流 | 步骤怎么编排 | 审批、批处理、工单流转 | 代码或平台编排 |
| 工具调用 | 模型怎么“动手” | 天气查询、订单操作 | Function Calling 机制 |
3. 技术选型分析:为什么 FastAPI 和 SpringBoot 双版本值得对照学习
现在 AI 应用的主流技术栈被 Python 生态主导,但在很多公司里,核心业务系统是 Java。两端各有不可替代的优势。
3.1 Python 体系的 AI 生态优势
Python 侧的优势是 AI 生态最完善。LangChain、LlamaIndex、MCP Python SDK 等工具链基本都在 Python 上先迭代,OpenAI、各家国产模型服务商也优先提供 Python 示例。实验新场景、做原型验证,Python 是最快的路径。FastAPI 在提供 API 服务上也非常合适:原生异步、自带数据校验和接口文档,代码量比 Flask 更紧凑。
3.2 Java 体系的工程化优势
Java 侧的优势是工程能力和老系统兼容性。绝大多数企业的核心订单、用户、权限系统都在 Java 体系内,AI 应用最终要接入这些系统时,Java 生态的成熟监控、配置中心、网关都能直接复用。Spring AI 和 LangChain4j 也在快速成熟,Spring 官方已经把模型接入、向量库、MCP 客户端等能力纳入统一抽象。
3.3 双版本对照学习的价值
同时学习两套实现,不是要求读者两种语言都精通,而是要理解一个核心事实:AI 应用架构和语言绑定很弱。你在 Python 里写的工具定义、RAG 流程、Agent 循环,换成 Java 时思路完全一样,变化的只是语法和 SDK。
对照学习时,建议先以自己熟悉的语言为主线跑通,再用另一套版本做“翻译练习”。比如 Python 团队要把能力开放给 Java 兄弟团队,你至少要知道他们在 pom.xml 里加什么依赖、控制器怎么写,才能真正协作。
3.4 如何根据团队情况选型
| 维度 | 优先 FastAPI | 优先 SpringBoot |
|---|---|---|
| 团队主要语言 | Python | Java / Kotlin |
| 当前阶段 | 原型验证、AI 工具研发 | 生产系统集成、企业级稳定 |
| 接入系统 | 数据平台、算法工程 | 订单、用户、ERP、审批流 |
| 生态特点 | AI SDK 更新快 | 企业中间件和监控完善 |
| 部署方式 | Docker / Serverless 均可 | 与现有 K8s、配置中心无缝衔接 |
没有绝对好坏,只有当前阶段谁更顺。
4. 架构设计与环境准备
4.1 全家桶的总体调用链路
从交互视角看,一个完整请求会经过这样一条链路:
用户请求 → 路由判断 → 普通对话或知识库问答 → 若进入 Agent 任务则开始规划循环 → 循环中通过 MCP 或工具调用执行操作 → 工作流引擎保证步骤顺序和异常处理 → 模型生成最终回复 → 系统记录日志和 Token 消耗。
这个链路没有强制顺序,不同场景可以跳过某些层。例如内部文档问答可能不需要 Agent,只需要 RAG;自动化运维任务可能不需要强大的知识检索,但需要很多工具。全家桶的价值在于“按需组合”。
4.2 项目目录结构设计
后端项目的目录结构直接影响后续可维护性。不建议把所有代码塞进一个 main.py 或一个大 Controller。推荐按能力层分包:
Python 版:
code复制ai-fullstack/
├── app/
│ ├── main.py # FastAPI 入口
│ ├── config.py # 全局配置
│ ├── llm/ # LLM 客户端与提示词
│ ├── rag/ # 向量化、检索、索引管理
│ ├── agent/ # Agent 循环与工具选择
│ ├── mcp/ # MCP 客户端封装
│ ├── workflow/ # 工作流引擎
│ └── tools/ # 本地工具实现
├── data/ # 知识文档
├── requirements.txt
└── .env
Java 版:
code复制ai-fullstack-java/
├── pom.xml
└── src/main/java/com/example/ai/
├── AiApplication.java # SpringBoot 启动类
├── controller/ # 对外 HTTP 接口
├── service/ # 业务服务
├── config/ # ChatClient、向量库等配置
├── tool/ # 工具类
├── rag/ # 向量检索与提示词拼装
├── agent/ # Agent 循环服务
└── workflow/ # 工作流编排组件
目录结构本身不决定功能,但决定你加一个新能力时是否需要改旧代码。按层分包之后,模型层、知识层、动作层可以各自演进。
4.3 环境准备清单
开始写代码前,准备以下环境:
- Python 3.10+,以及 JDK 17+;
- IDE 选择你习惯的即可,Python 用 PyCharm 或 VS Code,Java 用 IntelliJ IDEA;
- 一个大模型 API Key,支持 OpenAI 协议或兼容协议都可以;
- 一个本地向量库,推荐从 ChromaDB 开始,零部署成本;
- 如果跑 MCP 示例,还需要一套可用的 MCP Server 端程序。
Python 侧依赖文件示例:
text复制# requirements.txt
fastapi>=0.100
uvicorn[standard]
openai>=1.0
chromadb>=0.4
mcp>=0.9
python-dotenv
安装命令:
bash复制pip install -r requirements.txt
Java 侧用 Maven 管理依赖,核心依赖是 Spring Boot 和 Spring AI:
xml复制<!-- pom.xml 关键依赖,版本以你环境实际可用为准 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<relativePath/>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
</dependencies>
这里要提醒一点:Spring AI 版本迭代比较快,示例中的版本号只能作为参考。实际项目应去 Maven 中央仓库或 Spring 官方文档查当前稳定版本,再锁定具体版本号,避免依赖冲突。
5. LLM 与工具调用模块实现
5.1 统一模型接入方式:先想清楚再写代码
无论用哪家模型服务商,都建议在接入层做一个统一封装。凡是兼容 OpenAI 协议的服务商,都可以通过 base_url 和 api_key 切换,业务代码不感知具体厂商。这样后续换模型时,只需要改配置,不需要改业务。
5.2 FastAPI 端:LLM 调用示例
先写最基础的对话接口:
python复制# app/llm/client.py
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(
base_url=os.getenv("BASE_URL", "https://api.example.com/v1"),
api_key=os.getenv("API_KEY", "your-api-key")
)
def chat(message: str) -> str:
response = client.chat.completions.create(
model=os.getenv("MODEL_NAME", "your-model-name"),
messages=[
{"role": "system", "content": "你是一个可靠的中文助手。"},
{"role": "user", "content": message}
],
temperature=0.3
)
return response.choices[0].message.content
对应的 FastAPI 入口:
python复制# app/main.py
from fastapi import FastAPI
from app.llm.client import chat
app = FastAPI(title="AI 全家桶 Demo")
@app.get("/api/ai/chat")
def chat_api(message: str):
return {"reply": chat(message)}
启动方式:
bash复制uvicorn app.main:app --reload --port 8000
调用验证:
bash复制curl "http://localhost:8000/api/ai/chat?message=你好"
5.3 工具调用:模型输出指令,代码负责执行
只实现对话还远远不够。真实场景中,模型经常需要“动手”。以下代码演示一个完整工具调用流程:模型判断用户需要查天气,则输出一个 tool_calls 指令,应用代码解析参数并执行真实函数。
python复制# app/llm/tool_chat.py
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.example.com/v1",
api_key="your-api-key"
)
def get_weather(city: str) -> str:
"""模拟天气工具,实际项目可以换成真实天气服务调用"""
return f"今天{city}:晴,25°C,东南风2级"
def tool_chat(user_message: str):
response = client.chat.completions.create(
model="your-model-name",
messages=[{"role": "user", "content": user_message}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名,例如北京"}
},
"required": ["city"]
}
}
}],
tool_choice="auto"
)
message = response.choices[0].message
if message.tool_calls:
for tool_call in message.tool_calls:
args = json.loads(tool_call.function.arguments)
result = get_weather(**args)
print("工具返回:", result)
return result
return message.content
if __name__ == "__main__":
print(tool_chat("北京天气怎么样?"))
这里最关键的是理解交互协议:第一轮模型不直接说答案,而是返回“我打算调用 get_weather,参数 city=北京”。应用代码必须自己执行函数,再把结果回传给模型。很多初学者在这里犯错,以为调用 tools 参数后模型就会自动执行工具,实际并不是。
5.4 SpringBoot 端:ChatClient 与 @Tool
Java 侧的思路完全一样,只是表达方式不同。Spring AI 用 ChatClient 封装模型交互,用 @Tool 注解描述工具。
java复制// tool/WeatherTool.java
package com.example.ai.tool;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
@Component
public class WeatherTool {
@Tool(description = "查询指定城市的天气")
public String getWeather(String city) {
return "今天" + city + ":晴,25°C,东南风2级";
}
}
java复制// service/ChatService.java
package com.example.ai.service;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
@Service
public class ChatService {
private final ChatClient chatClient;
public ChatService(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String chat(String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
public String chatWithTool(String message) {
return chatClient.prompt()
.user(message)
.tools(new WeatherTool())
.call()
.content();
}
}
java复制// controller/ChatController.java
package com.example.ai.controller;
import com.example.ai.service.ChatService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/ai")
public class ChatController {
private final ChatService chatService;
public ChatController(ChatService chatService) {
this.chatService = chatService;
}
@GetMapping("/chat")
public String chat(@RequestParam String message) {
return chatService.chat(message);
}
@GetMapping("/tool-chat")
public String toolChat(@RequestParam String message) {
return chatService.chatWithTool(message);
}
}
配置写在 application.yml 中:
yaml复制spring:
application:
name: ai-fullstack-java
ai:
openai:
base-url: https://api.example.com/v1
api-key: ${AI_API_KEY}
chat:
options:
model: your-model-name
注意不要在工程文件里硬编码 Key,应该统一通过环境变量注入。
6. RAG 检索增强生成模块实现与调优
6.1 RAG 的三件套流程
RAG 的流程可以拆成三个固定阶段:
- 索引阶段:加载文档,按段落切分,对每个片段做 embedding,写入向量库;
- 检索阶段:将用户问题转为向量,在向量库中查询相似片段;
- 生成阶段:把检索到的片段拼入 Prompt,交给模型生成带依据的回答。
这里最容易踩的坑是跳过“切分”。很多人直接将整个文档塞进 Prompt,结果要么超出上下文,要么检索结果太粗。切分不是越短越好,而是与文档结构和业务问题匹配。
6.2 FastAPI 端:ChromaDB 实现本地知识库
以下示例使用 OpenAI 兼容接口的 embedding 能力,配合 ChromaDB 本地持久化:
python复制# app/rag/pipeline.py
import chromadb
from openai import OpenAI
client = OpenAI(base_url="https://api.example.com/v1", api_key="your-api-key")
chroma = chromadb.PersistentClient(path="./chroma_data")
kb = chroma.get_or_create_collection(name="demo_kb")
def build_index(documents: list[str], ids: list[str]):
"""文档入库:向量化并写入 ChromaDB"""
embeddings = client.embeddings.create(
model="text-embedding-3-small",
input=documents
).data
kb.upsert(
ids=ids,
documents=documents,
embeddings=[item.embedding for item in embeddings]
)
def search(query: str, top_k: int = 3) -> list[str]:
"""问题向量化后查询向量库,返回最相关的文档片段"""
query_vec = client.embeddings.create(
model="text-embedding-3-small",
input=query
).data[0].embedding
result = kb.query(query_embeddings=[query_vec], n_results=top_k)
return result["documents"][0]
def build_prompt(query: str, docs: list[str]) -> str:
context = "\n\n".join(docs)
return f"""请基于以下资料回答问题,如果资料中没有答案,直接说“资料中未找到”。
资料:
{context}
问题:{query}
"""
入库后的问答接入方式可以使用前面封装好的 chat 函数:
python复制# app/rag/service.py
from app.rag.pipeline import search, build_prompt
from app.llm.client import chat
def answer_by_rag(query: str) -> str:
docs = search(query)
prompt = build_prompt(query, docs)
return chat(prompt)
在真实项目中,入库的文档可能来自 PDF、Word、网页正文,需要先用解析器抽成纯文本,再做切分。切分可以用固定长度,也可以用 RecursiveCharacterTextSplitter 这类依赖语义边界的切分器。
6.3 SpringBoot 端:VectorStore 抽象
Spring AI 内置了 VectorStore 抽象,对向量库做了统一封装,下面是一种常见写法:
java复制// service/RagService.java
package com.example.ai.service;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class RagService {
private final VectorStore vectorStore;
private final ChatClient chatClient;
public RagService(VectorStore vectorStore, ChatClient.Builder builder) {
this.vectorStore = vectorStore;
this.chatClient = builder.build();
}
public String answer(String question) {
List<Document> docs = vectorStore.similaritySearch(
SearchRequest.builder().query(question).topK(3).build()
);
String context = docs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n\n"));
String prompt = """
请基于以下资料回答问题,如果资料中没有答案,直接说“资料中未找到”。
资料:
%s
问题:%s
""".formatted(context, question);
return chatClient.prompt().user(prompt).call().content();
}
}
Java 端选择向量库时,建议根据部署条件决定:本机验证用简单内存向量库即可,生产环境可以使用 PostgreSQL 的 pgvector 或独立的向量数据库,关键是不要在一开始就引入过重的中间件。
6.4 检索质量调优方向
检索效果差是最常见的反馈,调优顺序建议按影响程度排列:
| 优先级 | 调整项 | 说明 |
|---|---|---|
| 高 | 切分策略 | chunk 过大则细节丢失,过小则语义割裂 |
| 高 | 检索数量 topK | 太少可能漏信息,太多会稀释答案 |
| 中 | Embedding 模型 | 换成业务领域相关性更好的模型 |
| 中 | 查询改写 | 对用户原始问题做改写扩展后再检索 |
| 低 | 重排序 Rerank | 对检索结果二次打分,提升精度但会增加耗时 |
7. Agent、MCP 与工作流编排模块实现
7.1 Agent 最小实现:思考-行动-观察循环
在引入 Agent 框架之前,先理解 Agent 的底层循环非常重要。下面是一个最小可理解的实现思路:
python复制# app/agent/react_agent.py
import json
class ReActAgent:
def __init__(self, llm_function, tools: dict, max_steps: int = 5):
self.llm = llm_function
self.tools = tools
self.max_steps = max_steps
def run(self, task: str):
messages = [
{"role": "system", "content": "你是一个任务执行代理,请通过工具逐步完成任务。"},
{"role": "user", "content": task}
]
for step in range(self.max_steps):
print(f"=== Step {step + 1} ===")
message = self.llm(messages)
if message.get("tool_calls") is None:
return message["content"]
for tool_call in message["tool_calls"]:
fn_name = tool_call["function"]["name"]
args = json.loads(tool_call["function"]["arguments"])
print(f"调用工具: {fn_name}, 参数: {args}")
if fn_name not in self.tools:
raise ValueError(f"未注册工具: {fn_name}")
observed = self.tools[fn_name](**args)
messages.append({"role": "assistant", "content": message})
messages.append({
"role": "tool",
"tool_call_id": tool_call["id"],
"content": str(observed)
})
return "已达最大步骤数,任务未完成"
这段代码的 llm_function 是对模型 API 的进一步封装,返回结果里同时包含模型生成文本和工具调用指令。实际项目多数会使用现成 Agent 框架,但理解了这段循环逻辑后,再复杂的框架你也能看懂它的设计动机。
Agent 工程最需要注意三个控制点:最大步数限制防止死循环、工具结果必须回传给模型形成闭环、异常工具调用要能终止并给出提示。
7.2 MCP 客户端接入:让工具生态标准化
MCP 在全家桶中承担“工具连接标准化”的角色。Python 侧使用官方 MCP SDK 连接一个 FastMCP 工具端,只需要几步:
MCP Server 端,一个最简单的天气工具:
python复制# tools/mcp_server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("demo-tools")
@mcp.tool()
def get_weather(city: str) -> str:
"""查询城市天气"""
return f"{city}: 晴,25°C,东南风2级"
if __name__ == "__main__":
mcp.run()
MCP 客户端,通过标准输入输出协议连接上面的工具端:
python复制# app/mcp/mcp_client.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
server_params = StdioServerParameters(
command="python",
args=["tools/mcp_server.py"]
)
async def main():
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print("可用工具:", [tool.name for tool in tools.tools])
result = await session.call_tool("get_weather", {"city": "上海"})
print("调用结果:", result.content)
if __name__ == "__main__":
asyncio.run(main())
这里要特别提醒:MCP Python SDK 的 API 在 1.0 前后有调整,不同版本的使用方式可能略有不同。遇到连接失败或 API 变化时,优先查看你安装的 SDK 版本对应官方文档,而不是死记示例。
Java 侧的 Spring AI 也提供了 MCP 客户端支持。你可以在配置中注册一个 ToolCallbackProvider,将 MCP Server 暴露的工具自动桥接为 Spring AI 可识别的工具:
java复制// config/McpConfig.java
package com.example.ai.config;
import org.springframework.ai.mcp.McpToolCallbackProvider;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.modelcontextprotocol.server.McpSyncServer;
@Configuration
public class McpConfig {
@Bean
public ToolCallbackProvider mcpTools(McpSyncServer mcpServer) {
return new McpToolCallbackProvider(mcpServer);
}
}
MCP 带来的实际收益是:当你需要新增工具时,不必为每个工具单独实现鉴权、接入和解析协议,而是按 MCP 标准暴露出来,Agent 和应用层直接发现和使用。
7.3 工作流编排:确定性的步骤控制
工作流适合“步骤固定、结果可预期”的场景。以下用一个轻量 Python 实现演示:
python复制# app/workflow/engine.py
from dataclasses import dataclass
from typing import Callable, Dict
@dataclass
class Node:
name: str
handler: Callable[[dict], str | None] # 返回下一个节点名,None 表示流程结束
class Workflow:
def __init__(self, nodes: Dict[str, Node], start: str):
self.nodes = nodes
self.start = start
def run(self, context: dict) -> dict:
current = self.start
while current:
node = self.nodes[current]
current = node.handler(context)
return context
def step_validate(ctx: dict) -> str | None:
"""第一步:校验输入合法性,不合法则结束流程"""
if not ctx.get("content"):
print("输入校验失败")
return None
return "step_generate"
def step_generate(ctx: dict) -> str | None:
"""第二步:调用模型生成结果,示例中只写入固定内容"""
ctx["result"] = "已生成回答:" + ctx["content"]
print("生成完成")
return None
nodes = {
"step_validate": Node("step_validate", step_validate),
"step_generate": Node("step_generate", step_generate),
}
workflow = Workflow(nodes=nodes, start="step_validate")
context = workflow.run({"content": "你好"})
print(context)
这段代码展示了工作流的最小骨架:每个节点是一个纯函数,节点根据当前上下文决定下一步走向。正式项目中,你可以在节点上增加重试、超时、日志埋点、并行分支等能力,也可以直接使用现成的流程编排引擎。
工作流和 Agent 结合时,推荐思路是:外层框架用工作流控制大步骤,内层细节用 Agent 处理不确定性。例如工单系统里,“用户问题分类 → 检索知识库 → 生成回复 → 人工审核 → 发送通知”是确定性流程,但中间的回复生成可以交给 Agent 根据检索结果发挥。
8. 全家桶常见问题与排查思路
实际开发中遇到的问题通常不在单点难度,而在于各层之间的协作。以下按故障概率排序,整理了最常见的几类问题。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 模型返回内容为空 | messages 结构缺少 system 或上下文异常 | 打印实际请求 JSON | 补全消息结构,设置默认 system 提示 |
| 工具调用没有生效 | 模型不支持 Function Calling,或工具描述不清晰 | 查看响应中是否有 tool_calls 字段 | 换支持工具调用的模型,优化工具描述 |
| RAG 检索结果与问题无关 | 切分粒度不合适或 Embedding 模型偏弱 | 打印检索片段确认 | 调整 chunk_size 与 overlap,更换向量模型 |
| Agent 无限循环 | 缺少最大步数限制,或工具结果没有回传 | 查看循环日志 | 加入 max_steps 和终止条件,确保结果回填 |
| MCP 连接失败 | 工具端地址错误或 SDK 版本不兼容 | 单独先测试 MCP Server 能否启动 | 统一 SDK 版本,检查协议输出 |
| 双语言实现结果不一致 | 两端工具参数定义和模型版本不同 | 用同一请求对比两端日志 | 将参数 Schema 显式对齐 |
| 启动时依赖冲突 | LangChain、OpenAI、MCP 版本互相干扰 | 查看依赖树和异常堆栈 | 最小依赖启动,按模块引入,避免全家桶式引包 |
排查建议按照“先看请求,再看日志,最后改代码”的顺序。不要一上来就换模型或调向量库,先确认模型收到的 Prompt 到底是什么、工具调用指令是否生成、工具结果是否成功回传。大部分问题都能在日志层面暴露出来。
9. AI 应用工程化最佳实践与开发建议
9.1 配置外置,代码稳定
所有模型地址、API Key、向量库连接串都应该由环境变量或配置中心管理,不写死在代码里。Python 常用 .env 文件管理开发环境,Java 侧建议直接用 Spring 的 application.yml 配合环境变量覆盖。生产环境的 Key 必须使用密钥管理系统。
9.2 统一响应结构,便于前端和后端协作
无论走 FastAPI 还是 SpringBoot,建议 AI 接口的响应统一为包含 reply、context、cost、trace_id 的 Schema。这样前端可以统一渲染,后端可以统一统计。
json复制{
"reply": "今天是晴天。",
"context": ["检索到的资料片段"],
"cost": {
"prompt_tokens": 1024,
"completion_tokens": 128
},
"trace_id": "c7d5a2f0"
}
9.3 可观测性:Token 和日志是 AI 应用的命脉
AI 应用的成本主要由 Token 决定。线上环境必须记录每个请求的 Token 消耗、模型名称、耗时和错误码。建议在接入层做一个统一的拦截器,每次请求自动记录这些信息。
9.4 安全边界不能省
涉及工具调用时,必须按最小权限原则设计工具。例如允许模型查询天气是低风险操作,但允许模型执行 SQL、删除文件、发消息属于高风险操作,需要加审批或操作白名单。同时要注意 Prompt 注入:不能把用户输入直接拼进 System Prompt 而不做任何隔离。
9.5 缓存、限流与重试
重复相同问题、相同知识片段会消耗大量 Token,可以加一层结果缓存。对外 API 要限流,避免模型被刷爆。调用模型 API 时要设计超时和重试,但对重试要设置上限,防止故障放大。
9.6 测试方式:评估集优先
AI 应用不能只测“能不能跑通”,要建立一个小范围评估集:每个用例包含输入、预期行为、判定标准。每次切换 Prompt、升级模型后,都要用同一评估集回归,这样才能判断改动是变好还是变差。
10. 总结:这套全家桶真正教给你的是链路思维
回到开头的问题:为什么单看每个 AI 概念都不难,但做起项目来却处处卡壳?因为 AI 应用开发不是单点能力的叠加,而是一条完整链路:LLM 提供理解力,RAG 补充知识源,Agent 负责目标拆解,工具调用让模型可执行,MCP 统一工具连接,工作流保证过程可控。这套全家桶最大的价值,是把这些能力组织成了一个可落地、可演进、双语言可对照的骨架。
如果你是 Python 团队,建议从 FastAPI 版本入手,把 LLM 和工具调用先跑通,再加 RAG,最后引入 Agent 和 MCP。如果你是 Java 团队,思路完全一致,只需要把 Spring AI 的配置和依赖替换进来。不要想着一次就上齐全家桶,每一层独立验证后再串联,排查问题会轻松很多。
下一步值得深入的方向包括:用开源模型替换商业模型、为 RAG 增加重排序能力、为企业系统开发更多 MCP 工具、建立完整的评测体系和监控大盘。AI 应用技术迭代很快,但底层链路不会频繁变化,这套骨架值得作为你团队里的长期基础设施。
如果这篇文章对你有帮助,建议收藏备用,动手搭建时照着环境准备和示例代码操作即可。
