🔖 系列 :FastAPI + LangChain 实战
📅 适合人群 :有 Java 基础,想了解 AI 服务架构的开发者
⏱ 阅读时长:约 10 分钟
用 FastAPI 将 LangChain Agent 封装成对外接口
------ 以 Java 主服务调用 Python AI 服务为例
📌 前言
很多公司的后端是 Java(Spring Boot)技术栈,但 AI 生态(LangChain、Transformers 等)几乎都在 Python 世界。这时候,最常见也最合理的做法是:
Java 负责主业务,Python 负责 AI 推理,两者通过 HTTP 接口通信。
FastAPI 正是 Python 这侧的 HTTP 入口。本文将完整讲解这套架构的搭建方式,从 FastAPI 基础、LangChain Tool、Agent 原理,到 Java 如何调用 Python 服务。
一、整体架构
系统分为三层:

Java 和 Python 是两个独立部署的服务,通过标准 HTTP 协议连接。这样设计的好处:
- ✅ 职责清晰,Java 管业务,Python 管 AI
- ✅ 两侧可以独立升级、独立扩容
- ✅ Python AI 服务可以被多个 Java 服务复用
二、完整请求流程
以用户查询「我的订单 ORD-001 到哪里了」为例:

💡 Java 对 Python 的调用,和调用任何第三方 HTTP 接口没有区别。
三、Python 侧:FastAPI 封装 Agent
3.1 FastAPI 三个核心概念
① 路由:声明"什么路径 + 什么方法 → 执行什么函数"
python
@app.get("/") # GET 请求
@app.post("/ask") # POST 请求
② 请求体 :客户端发来的 JSON,用 Pydantic BaseModel 定义结构
python
class AskRequest(BaseModel):
question: str # 必填
user_id: str = "" # 可选,有默认值
③ 响应体:规范返回给客户端的 JSON 格式
python
class AskResponse(BaseModel):
answer: str
3.2 LangChain Tool 定义
Tool 是 Agent 的「手」,@tool 装饰器把普通函数变成 LangChain 可识别的工具。
⚠️ 关键:函数的 docstring 就是工具描述,LLM 靠它决定什么时候调用,必须写清楚。
python
from langchain.tools import tool
@tool
def query_order(order_id: str) -> str:
"""查询订单状态。当用户询问订单进度时调用。输入格式:ORD-XXX"""
orders = {
"ORD-001": {"product": "iPhone 15", "status": "已发货", "price": 5999},
"ORD-002": {"product": "AirPods Pro", "status": "待发货", "price": 1799},
}
order = orders.get(order_id)
if not order:
return f"找不到订单 {order_id}"
return f"订单{order_id}:{order['product']},{order['status']},¥{order['price']}"
3.3 Agent 推理机制(ReAct)
Agent 的核心是 ReAct 循环(Re ason + Act),以「ORD-001 到哪里了」为例:
Thought: 用户想查询订单状态,调用 query_order 工具
Action: query_order("ORD-001")
Observation: iPhone 15,已发货,¥5999
Final Answer: 您的订单 ORD-001(iPhone 15)已发货,金额 ¥5999。
循环最多执行 max_iterations 次,直到得出 Final Answer。
3.4 完整 Python 服务代码
python
# main.py ------ Python AI 服务
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from langchain.tools import tool
from langchain.agents import AgentExecutor, create_react_agent
from langchain_openai import ChatOpenAI
from langchain import hub
app = FastAPI()
# ── 1. 定义 Tool ──────────────────────────────────────────
@tool
def query_order(order_id: str) -> str:
"""查询订单状态。当用户询问订单进度时调用。输入格式:ORD-XXX"""
orders = {
"ORD-001": "iPhone 15,已发货,¥5999",
"ORD-002": "AirPods Pro,待发货,¥1799",
}
return orders.get(order_id, f"找不到订单 {order_id}")
# ── 2. 初始化 Agent(应用启动时创建一次)────────────────────
llm = ChatOpenAI(model="gpt-4o", temperature=0)
prompt = hub.pull("hwchase17/react")
agent = create_react_agent(llm, [query_order], prompt)
executor = AgentExecutor(
agent=agent,
tools=[query_order],
max_iterations=5,
handle_parsing_errors=True,
)
# ── 3. 请求体 / 响应体 ───────────────────────────────────────
class AskRequest(BaseModel):
question: str
class AskResponse(BaseModel):
answer: str
# ── 4. 对外接口 ──────────────────────────────────────────────
@app.post("/ask", response_model=AskResponse)
def ask(req: AskRequest):
try:
res = executor.invoke({"input": req.question})
return AskResponse(answer=res["output"])
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
启动命令:
bash
uvicorn main:app --host 0.0.0.0 --port 8000
--host 0.0.0.0让 Java 服务可以通过网络访问到它,不只限于本机。
四、Java 侧:调用 Python 服务
4.1 RestTemplate(同步调用)
java
@Service
public class AiService {
private final RestTemplate restTemplate = new RestTemplate();
private static final String AI_URL = "http://ai-service:8000/ask";
public String queryWithAI(String question) {
Map<String, String> body = new HashMap<>();
body.put("question", question);
ResponseEntity<Map> resp = restTemplate.postForEntity(AI_URL, body, Map.class);
return (String) resp.getBody().get("answer");
}
}
4.2 WebClient(推荐,非阻塞)
java
private final WebClient webClient = WebClient.builder()
.baseUrl("http://ai-service:8000")
.build();
public Mono<String> queryWithAI(String question) {
return webClient.post()
.uri("/ask")
.bodyValue(Map.of("question", question))
.retrieve()
.bodyToMono(Map.class)
.map(b -> (String) b.get("answer"));
}
五、三个关键细节
① Agent 为什么要在函数外初始化?
python
# ✅ 正确:应用启动时创建一次
executor = AgentExecutor(...)
@app.post("/ask")
def ask(req):
executor.invoke(...)
python
# ❌ 错误:每次请求都创建,每次都拉 prompt,性能极差
@app.post("/ask")
def ask(req):
executor = AgentExecutor(...)
executor.invoke(...)
② 为什么 temperature=0?
ReAct Agent 的输出必须严格遵守 Action / Action Input 格式,LLM 才能正确解析。temperature=0 让输出更稳定,避免格式混乱导致解析失败。
③ Java 侧一定要做错误处理
java
try {
String answer = aiService.queryWithAI(question);
return ResponseEntity.ok(answer);
} catch (HttpServerErrorException e) {
// AI 服务出错时降级,不能让用户看到 500
return ResponseEntity.ok("AI 服务暂时不可用,请稍后重试");
}
六、总结
| 层次 | 语言 | 职责 |
|---|---|---|
| 展示层 | 前端 | 用户界面 |
| 业务层 | Java Spring Boot | 主业务 · 数据库 · 权限 · 路由 |
| AI 接口层 | Python FastAPI | 接收 HTTP 请求,调用 Agent |
| AI 推理层 | LangChain Agent | ReAct 推理,调用 Tool |
| 工具层 | Python @tool | 真正执行查询、计算等操作 |
核心思路:Java 和 Python 不直接调用彼此的代码,而是通过 HTTP 接口协作。FastAPI 是 Python 侧的 HTTP 出口,Java 只需要知道接口地址和 JSON 格式,不需要了解 Python 内部实现。
如果这篇文章对你有帮助,欢迎点赞 👍 收藏 ⭐ 关注 🔔
有问题欢迎在评论区交流!