Files

83 lines
4.4 KiB
Markdown

---
id: backend.java-spring.scenario.call-java-service
title: Python Agent 调用公司已有的 Java 业务系统:读接口、封装工具、错误映射和联调
type: scenario
domain: backend
stack: [java-spring, function-calling, fastapi]
tags: [系统对接, 工具封装, 错误码映射, OpenAPI, 联调, 幂等]
level: intermediate
prerequisites: [backend.java-spring.awareness, ai.function-calling.tool-loop]
est_hours: 2
related: [backend.java-spring.interview, ai.function-calling.scenario.safe-business-tools]
week: [14]
---
# Python Agent 调用公司已有的 Java 业务系统:读接口、封装工具、错误映射和联调
## 业务背景与问题
公司的订单、售后系统是多年的 Spring Boot 服务。新做的 AI 客服 Agent(Python)需要“查订单、查物流、发起退款申请”。问题:
1. 接口文档过期,字段含义要读代码才知道;
2. Java 服务的错误码体系复杂,Agent 不知道哪些错误可以重试、哪些要告诉用户;
3. 退款接口不是幂等的,Agent 重试导致重复申请;
4. 联调时双方互相指责。
## 约束
- 不能大改 Java 服务,只能小范围加接口或参数。
- Agent 不能拥有超过用户本人的权限。
## 方案设计
1. **读接口**:从 Controller 和 DTO 读出真实的字段、校验规则、错误码;如果服务集成了 OpenAPI(springdoc 一类工具),导出规范生成 Python 客户端;否则自己写 Pydantic 模型。
2. **封装工具**:每个业务能力一个工具,入参严格校验;工具层负责鉴权透传(用户令牌或服务账号 + 用户 id)、超时、重试(只对查询类和幂等接口)。
3. **错误映射**:把 Java 错误码分为三类——可重试(超时、限流)、需要告诉用户(订单不存在、超过退款期限)、需要转人工(系统异常);返回给模型的是结构化的可读信息,而不是堆栈。
4. **幂等**:和 Java 团队约定退款接口增加 `Idempotency-Key` 请求头(服务端用唯一约束实现),Agent 在一次会话中对同一操作使用同一个 key。
5. **联调**:先写契约测试(请求 / 响应样例),双方各自对着样例开发;用 request id 串起两边日志。
## 关键代码
```python
class JavaErrorKind(str, Enum):
RETRYABLE = "retryable"
USER_FACING = "user_facing"
HANDOFF = "handoff"
ERROR_MAP = {
"ORDER_NOT_FOUND": (JavaErrorKind.USER_FACING, "没有找到这个订单,请核对订单号"),
"REFUND_EXPIRED": (JavaErrorKind.USER_FACING, "已超过可退款期限"),
"RATE_LIMITED": (JavaErrorKind.RETRYABLE, "系统繁忙"),
}
def call_refund(client: httpx.Client, user: User, order_id: str, reason: str, idem_key: str) -> dict:
r = client.post("/api/refunds", json={"orderId": order_id, "reason": reason},
headers={"Authorization": f"Bearer {user.token}", "Idempotency-Key": idem_key}, timeout=5)
if r.is_success:
return {"ok": True, "refund_id": r.json()["data"]["refundId"]}
code = r.json().get("code", "UNKNOWN")
kind, msg = ERROR_MAP.get(code, (JavaErrorKind.HANDOFF, "系统异常,已为你转人工"))
return {"ok": False, "kind": kind, "message": msg} # 给模型的是结构化结果,不是堆栈
```
工具调用安全的完整讨论见[安全的业务工具](../../../ai/function-calling/scenarios/safe-business-tools.md)。
## 取舍
| 选择 | 优点 | 缺点 |
| --- | --- | --- |
| **Python 工具层直接调用 Java 接口(本方案)** | 不改动 Java 服务主体 | 需要维护错误映射和模型定义 |
| Java 团队提供专门给 Agent 的聚合接口 | 语义更清晰 | 依赖对方排期 |
| 用 Spring AI 在 Java 侧实现 Agent | 技术栈统一 | 团队 AI 生态以 Python 为主 |
## 易错点
- Agent 使用高权限服务账号,绕过了用户级权限。
- 对非幂等接口自动重试。
- 把 Java 的异常堆栈直接交给模型,既泄露内部信息又让模型困惑。
- 金额字段在 JSON 中是字符串还是数字没有约定,精度丢失。
## 面试怎么讲
> “我不是 Java 开发,但能读懂 Spring Boot 项目:从 Controller 和 DTO 读出真实的接口契约。对接时我在 Python 侧封装工具,鉴权透传用户身份;把 Java 的错误码分成可重试、告知用户、转人工三类,给模型结构化结果;和 Java 团队约定退款接口支持幂等键。联调前先写契约样例,用 request id 串联日志。”