1. 如何设计领域级 AiService 接口,既支持 Provider 替换又不丢失工具、推理和多模态特性
如何设计一个领域级的 AiService 接口,使底层 Provider 可以灵活替换,同时不丢失工具调用、推理和多模态等特性?
- 面向接口的抽象与依赖倒置
- Provider 抽象层的职责边界
- 工具、推理(reasoning)与多模态能力的 Provider 差异与契约
领域级 AiService 应基于"业务语义"而非"某个 Provider 的 SDK"来定义接口,例如把方法定义为 processDocument(Document)、generateReply(ConversationContext) 而非 chatCompletion(OpenAiChatRequest)。接口返回值使用领域 DTO(如 DialogueResult、ToolResult),底层再通过 Spring AI 的 ChatClient/OpenAiChatModel 或 LangChain4j 的 AiServices 适配。Provider 替换时,凡是能力差异(是否支持工具、reasoning、多模态、结构化输出)都应抽象为接口上的能力声明或配置项,而不是把 Provider 特有字段泄漏到领域层。工具调用应通过统一的 ToolCallback 注册机制暴露,推理与多模态参数通过 ModelOptions 封装,从而让上层只依赖领域抽象。
关键是把"变化"隔离在 Provider 适配层。若领域接口直接暴露 OpenAI 的 message 结构,替换 Anthropic 或本地 Ollama 时就要改业务代码。通过领域 DTO + 适配器 + 能力协商,既能做 Provider 替换与 failover,又能在某 Provider 不支持的场景(如某个模型不支持多模态)在调用前做能力校验,做到"替换不丢特性、缺失特性提前暴露"。
public interface AiService {
DialogueResult generateReply(ConversationContext ctx);
Stream<DialogueResult> streamReply(ConversationContext ctx);
boolean supports(Capability capability); // TOOL / REASONING / MULTIMODAL
}
@Service
public class OpenAiAiService implements AiService {
private final ChatClient chatClient;
@Override
public DialogueResult generateReply(ConversationContext ctx) {
return chatClient.prompt()
.messages(ctx.messages())
.tools(new DocumentTool()) // 统一工具注册
.options(OpenAiChatOptions.builder().model("gpt-4o").build())
.call()
.map(Mappers::toDialogueResult);
}
}