Java + Python 联调

Java + Python 联调的核心是让 Spring Boot 负责业务入口,让 FastAPI 负责 AI 能力,并用统一协议、超时、重试和日志把两边稳定连接起来。

为什么要 Java 调 Python

在 AI Agent 项目里,Java 和 Python 的优势不一样。

Java 适合:

业务接口
权限认证
用户体系
数据库事务
后台管理
稳定部署

Python 适合:

LangChain
LangGraph
RAG
Embedding
文档解析
Agent 工作流
模型实验

所以一个现实的架构是:

前端
  -> Java Spring Boot
  -> Python FastAPI
  -> LangChain / LangGraph / RAG

Java 对外提供稳定入口。

Python 对内提供 AI 能力。

联调目标

本节不追求复杂功能。

目标是把链路跑通:

Spring Boot 接收请求
  -> 调用 FastAPI
  -> FastAPI 返回 AI 结果
  -> Spring Boot 统一返回前端

这一节要完成:

统一请求格式
统一响应格式
连接超时
读取超时
基础重试
请求日志
错误兜底

Python 侧接口

FastAPI 先提供一个 AI 聊天接口。

创建 main.py

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI(title="Python AI Service")


class PythonChatRequest(BaseModel):
    request_id: str = Field(min_length=1)
    message: str = Field(min_length=1)


class PythonChatData(BaseModel):
    answer: str


class ApiResponse(BaseModel):
    code: str
    message: str
    data: PythonChatData | None = None
    request_id: str


@app.get("/health")
def health():
    return {
        "status": "ok"
    }


@app.post("/api/agent/chat", response_model=ApiResponse)
def chat(request: PythonChatRequest):
    return ApiResponse(
        code="OK",
        message="success",
        data=PythonChatData(
            answer=f"Python AI 收到:{request.message}"
        ),
        request_id=request.request_id,
    )

启动:

uvicorn main:app --reload --host 127.0.0.1 --port 8000

测试:

curl -X POST http://127.0.0.1:8000/api/agent/chat \
  -H "Content-Type: application/json" \
  -d '{"request_id":"req-001","message":"什么是 RAG?"}'

期望返回:

{
  "code": "OK",
  "message": "success",
  "data": {
    "answer": "Python AI 收到:什么是 RAG?"
  },
  "request_id": "req-001"
}

统一响应格式

统一响应格式很重要。

否则 Java 调 Python 时会经常写很多特殊判断。

建议先统一成:

{
  "code": "OK",
  "message": "success",
  "data": {},
  "request_id": "req-001"
}

字段含义:

code:业务状态码
message:状态说明
data:真正的数据
request_id:请求追踪 ID

错误也保持同样结构:

{
  "code": "AI_SERVICE_ERROR",
  "message": "Python AI 服务处理失败",
  "data": null,
  "request_id": "req-001"
}

不要一会儿返回字符串,一会儿返回对象。

协议越稳定,联调越轻松。

Java 请求响应对象

Java 侧定义请求:

public record PythonChatRequest(
        String requestId,
        String message
) {
}

返回数据:

public record PythonChatData(
        String answer
) {
}

统一响应:

public record PythonApiResponse<T>(
        String code,
        String message,
        T data,
        String requestId
) {
}

注意字段命名。

Python 默认是 request_id

Java 默认是 requestId

你可以选择两种方案:

方案一:Python 也用 requestId
方案二:Java 用 @JsonProperty("request_id")

学习阶段建议统一用 Java 风格:

{
  "requestId": "req-001"
}

两边少一点转换问题。

Java RestClient

Spring Boot 里可以用 RestClient 调 Python。

示例:

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

import java.util.UUID;

@Service
public class PythonAgentClient {

    private final RestClient restClient;

    public PythonAgentClient(RestClient.Builder builder) {
        this.restClient = builder
                .baseUrl("http://127.0.0.1:8000")
                .build();
    }

    public String chat(String message) {
        String requestId = UUID.randomUUID().toString();

        PythonApiResponse<PythonChatData> response = restClient.post()
                .uri("/api/agent/chat")
                .body(new PythonChatRequest(requestId, message))
                .retrieve()
                .body(new ParameterizedTypeReference<>() {
                });

        if (response == null || response.data() == null) {
            throw new IllegalStateException("Python AI 服务无响应");
        }

        if (!"OK".equals(response.code())) {
            throw new IllegalStateException("Python AI 服务返回失败:" + response.message());
        }

        return response.data().answer();
    }
}

需要导入:

import org.springframework.core.ParameterizedTypeReference;

如果不想处理泛型,学习阶段也可以先写固定响应类型:

public record PythonChatApiResponse(
        String code,
        String message,
        PythonChatData data,
        String requestId
) {
}

这样更直观。

加超时

Java 调 Python 一定要设置超时。

否则 Python 服务卡住时,Java 请求也会一直挂着。

可以配置一个专用 RestClient

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestClient;

import java.time.Duration;

@Configuration
public class PythonClientConfig {

    @Bean
    RestClient pythonRestClient(RestClient.Builder builder) {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(Duration.ofSeconds(3));
        requestFactory.setReadTimeout(Duration.ofSeconds(30));

        return builder
                .baseUrl("http://127.0.0.1:8000")
                .requestFactory(requestFactory)
                .build();
    }
}

超时可以先这样定:

连接超时:3 秒
读取超时:30 秒

后面如果 Agent 工作流很长,再根据接口类型单独调整。

基础重试

不是所有错误都应该重试。

适合重试:

连接失败
网络抖动
临时 502 / 503 / 504
读取超时

不适合重试:

参数错误
认证失败
文件不存在
用户问题非法
明确的业务失败

学习阶段可以先写一个简单重试:

public String chatWithRetry(String message) {
    RuntimeException lastError = null;

    for (int i = 0; i < 2; i++) {
        try {
            return chat(message);
        } catch (RuntimeException e) {
            lastError = e;
        }
    }

    throw new IllegalStateException("Python AI 服务暂时不可用", lastError);
}

正式项目可以使用:

Spring Retry
Resilience4j
网关重试策略
消息队列异步补偿

本节只需要理解重试边界。

加日志

联调最怕不知道请求卡在哪里。

Java 侧至少记录:

requestId
Python 接口路径
请求耗时
成功或失败
错误类型

示例:

public String chat(String message) {
    String requestId = UUID.randomUUID().toString();
    long start = System.currentTimeMillis();

    try {
        PythonChatApiResponse response = restClient.post()
                .uri("/api/agent/chat")
                .body(new PythonChatRequest(requestId, message))
                .retrieve()
                .body(PythonChatApiResponse.class);

        long elapsedMs = System.currentTimeMillis() - start;
        log.info("python chat success requestId={} elapsedMs={}", requestId, elapsedMs);

        if (response == null || response.data() == null) {
            throw new IllegalStateException("Python AI 服务无响应");
        }

        return response.data().answer();
    } catch (Exception e) {
        long elapsedMs = System.currentTimeMillis() - start;
        log.warn("python chat failed requestId={} elapsedMs={}", requestId, elapsedMs, e);
        throw e;
    }
}

日志不要记录完整敏感内容。

尤其是:

API Key
用户隐私
内部 token
大段文档原文

Java 对外 Controller

Java 对前端可以继续提供自己的接口。

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/13")
public class JavaPythonChatController {

    private final PythonAgentClient pythonAgentClient;

    public JavaPythonChatController(PythonAgentClient pythonAgentClient) {
        this.pythonAgentClient = pythonAgentClient;
    }

    @PostMapping("/chat")
    public JavaChatResponse chat(@RequestBody JavaChatRequest request) {
        String answer = pythonAgentClient.chatWithRetry(request.message());
        return new JavaChatResponse(answer);
    }
}

前端只知道 Java:

POST /api/13/chat

前端不需要知道 Python 服务地址。

这样以后 Python 服务换端口、换机器、换实现,前端不用改。

联调顺序

不要一上来就两边一起调。

建议顺序:

1. 单独启动 FastAPI
2. curl 调 Python 接口成功
3. 启动 Spring Boot
4. Java 单元测试调 Python client
5. curl 调 Java 接口
6. 前端调 Java 接口

每一步都确认成功,再往下走。

否则出错时很难判断是:

Python 代码问题
Java 请求问题
网络问题
JSON 字段问题
前端请求问题

常见问题

为什么不是前端直接调 Python

可以,但不建议作为主架构。

因为 Java 通常负责:

登录态
权限
审计
限流
统一错误码
业务数据

前端直接调 Python 会绕过这些业务控制。

Python 服务没启动怎么办

Java 侧应该返回明确兜底错误。

比如:

AI 服务暂时不可用,请稍后再试

日志里记录具体连接异常。

不要把底层异常堆栈直接返回给用户。

requestId 谁生成

入口服务生成。

在这个架构里,一般由 Java 生成 requestId,然后传给 Python。

Python 日志里也打印同一个 requestId

这样两边日志能串起来。

超时设置多少合适

看接口类型。

普通聊天:

10-30 秒

文档解析:

可能需要异步任务

复杂 Agent:

可以拆成任务状态查询,不要一直阻塞 HTTP

练习清单

完成几件事情:

写 FastAPI /api/agent/chat
定义 Python 请求响应模型
返回统一 ApiResponse
Java 定义 PythonChatRequest
Java 定义 PythonChatResponse
Java 用 RestClient 调 FastAPI
配置连接超时和读取超时
补一个基础重试
打印 requestId 和 elapsedMs
通过 Java /api/13/chat 调通完整链路

建议目录:

ai-agent-study
├── java
│   └── spring-ai-chat-demo
│       └── PythonAgentClient.java
├── python
│   └── fastapi-ai-service
│       └── main.py
└── docs
    └── java-python-integration.md

小结

本节的结论:

Java + Python 联调不是简单 HTTP 调用,关键是统一协议、超时、重试、日志和错误兜底。

最小链路:

前端
  -> Java Controller
  -> Java PythonAgentClient
  -> FastAPI
  -> Python AI 能力
  -> Java 统一返回

只要这条链路稳定,后面 LangChain、LangGraph、RAG 都可以放到 Python 服务里逐步增强。

参考资料


这个家伙很懒,啥也没有留下😋