🔖 系列: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 三个核心概念

① 路由:声明"什么路径 + 什么方法 → 执行什么函数"

@app.get("/")        # GET 请求
@app.post("/ask")    # POST 请求

② 请求体:客户端发来的 JSON,用 Pydantic BaseModel 定义结构

class AskRequest(BaseModel):
    question: str          # 必填
    user_id: str = ""      # 可选,有默认值

③ 响应体:规范返回给客户端的 JSON 格式

class AskResponse(BaseModel):
    answer: str

3.2 LangChain Tool 定义

Tool 是 Agent 的「手」,@tool 装饰器把普通函数变成 LangChain 可识别的工具。

⚠️ 关键:函数的 docstring 就是工具描述,LLM 靠它决定什么时候调用,必须写清楚。

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 循环(Reason + 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 服务代码

# 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))

启动命令:

uvicorn main:app --host 0.0.0.0 --port 8000

--host 0.0.0.0 让 Java 服务可以通过网络访问到它,不只限于本机。


四、Java 侧:调用 Python 服务

4.1 RestTemplate(同步调用)

@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(推荐,非阻塞)

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 为什么要在函数外初始化?

# ✅ 正确:应用启动时创建一次
executor = AgentExecutor(...)

@app.post("/ask")
def ask(req):
    executor.invoke(...)
# ❌ 错误:每次请求都创建,每次都拉 prompt,性能极差
@app.post("/ask")
def ask(req):
    executor = AgentExecutor(...)
    executor.invoke(...)

② 为什么 temperature=0?

ReAct Agent 的输出必须严格遵守 Action / Action Input 格式,LLM 才能正确解析。temperature=0 让输出更稳定,避免格式混乱导致解析失败。

③ 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 内部实现。


如果这篇文章对你有帮助,欢迎点赞 👍 收藏 ⭐ 关注 🔔
有问题欢迎在评论区交流!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐