AiProviderAccessor
AiProviderAccessor 是 plugin 调用平台托管 LLM Provider 的对外通道。它让 plugin 发起 chat completion 时,完全不接触 LlmProviderFactory、租户解析、密钥加载、Provider 协议适配——这些 host 负责的事都留在平台边界内,accessor 暴露的是稳定的、副作用清晰的接口。
这是你已经在用的 AI 原生能力背后的 SPI:Aura Bot、ChatBI、Agent workflow、RAG 增强、plugin handler 内的 command 端富化。如果你的 plugin 需要调 LLM,这是契约——不是 Spring 自动注入。
概念
accessor 通过 host-bridge 接口投递给 plugin 代码,与 FileAccessor 和 BackgroundDataAccessor 同一模式。Plugin 不 autowire LlmProviderFactory——它在 command 执行期通过 CommandContext 拿到 host 绑定的实例。host 把 accessor 放进 CommandContext.settings map,约定的 key 是:
String SETTINGS_KEY = "__aiProviderAccessor";Host 实现(LlmProviderAccessorImpl)按当前租户上下文解析此次调用:哪个 llm_config profile 适用、哪个 Provider 凭据可达、哪个 cloud_config override 生效、哪个限速与 entitlement 守护起作用、上游 payload 按 Provider 怎么塑形。
正是这套切分让 plugin 的 AI 调用在构造上就是 AI 安全的。Plugin 描述意图,平台决定身份、密钥、协议。
接口签名
package com.auraboot.framework.plugin.extension;
public interface AiProviderAccessor {
String SETTINGS_KEY = "__aiProviderAccessor";
ChatResponse chat(ChatRequest request) throws Exception;
record ChatRequest(
String useCase,
String providerProfileCode,
String providerCode,
String modelName,
String systemPrompt,
List<Message> messages,
int maxTokens,
Map<String, Object> metadata
) {}
record Message(String role, String content) {
public static Message user(String content);
public static Message assistant(String content);
}
record ChatResponse(
String providerCode,
String modelName,
String text,
int inputTokens,
int outputTokens,
int totalTokens,
String rawResponseJson
) {}
}Record 不可变;Message 上的辅助构造覆盖最常见的两类。rawResponseJson 用于诊断和审计——不要用它跑业务逻辑,业务逻辑用 text / inputTokens / outputTokens 等强类型字段。
在 command handler 内取到 accessor
@Extension
public class SummarizeIssueHandler implements CommandHandlerExtension {
@Override
public String getCommandType() {
return "crm:case_summarize";
}
@Override
public Object execute(CommandContext context) throws Exception {
AiProviderAccessor llm = context.aiProviderAccessor();
var req = new AiProviderAccessor.ChatRequest(
"crm.case.summarize", // useCase
null, // providerProfileCode (host 自选)
null, // providerCode (host 自选)
null, // modelName (host 自选)
"用一段话总结客户工单。", // systemPrompt
List.of(AiProviderAccessor.Message.user(
String.valueOf(context.payload().get("caseDescription")))),
512, // maxTokens
Map.of("auditCorrelationId", context.recordId()) // metadata
);
AiProviderAccessor.ChatResponse resp = llm.chat(req);
return Map.of("summary", resp.text());
}
}三点要注意:
- 不 Spring autowire。Plugin 永远看不到
LlmProviderFactory、OpenAiClient、AnthropicClient等 useCase必填。它控制此次调用归属哪个llm_configprofile、哪个限速桶、哪个审计类别。漏填则平台落到默认 profile,默认 profile 通常更严- profile / provider / model 可为
null。留 null 让平台为当前租户选最合适的。只在 plugin 有硬需求时显式设(如合同要求托管型企业模型)
何时设 providerProfileCode / providerCode / modelName
| 字段 | 设它的场景 | 留 null 的场景 |
|---|---|---|
providerProfileCode | Plugin 约定特定 llm_config profile(如隐私受限的租户自有密钥集) | 走 host 策略 + 成本优化 |
providerCode | Use-case 因合同原因锁定具体 Provider | 走 failover 与 Provider 轮转 |
modelName | 需要具体模型版本以保可重现性或能力 | 走 host 为该 profile 推荐的模型 |
大多数 plugin 三项全留 null。设 useCase 让平台来选。
错误语义
chat(...) 抛 checked Exception,因为 LLM 调用跨网络、跨密钥、跨 Provider 边界——失败必须对调用方可见。接口只声明一个通用 throws Exception,不暴露细分的异常类型;具体失败原因由 host 端在异常 message / cause 里携带。常见失败语义:
- Entitlement 缺失 — 当前租户对此
useCase没有 entitlement(如未购买相应的 Aura Bot 套餐)。Fail command 透传到用户,不要静默重试 - Provider 不可用 — 上游 Provider 返回 5xx / 超时。再抛出会中止 command 管线
- Provider profile 无效 —
providerProfileCode在该租户未注册。改配置,不要绕过 - 限速触发 — 租户或 use-case 桶已耗尽。透传给用户;不要在 plugin 端做重试循环(限速由平台负责)
只 catch 你能恢复的。把 Exception 包成通用错误是反模式——它把 entitlement 与 config 漂移从平台监控面隐藏了。
审计与关联
每次调用都写到平台 LLM 审计轨迹,索引 key 为 useCase、providerCode、modelName、inputTokens、outputTokens,以及 metadata 里你自带的关联字段(如 auditCorrelationId)。把当前记录主键 context.recordId() 放进 metadata,让 LLM 调用与触发它的业务记录在审计里能 join 起来——这就是审计 join 的设计意图。
敏感 prompt 内容按平台脱敏策略在审计存储里掩码。Plugin 不要重复做——不要从自己的 handler 里把完整 prompt 写日志。
常见反模式
| 模式 | 为什么坏 |
|---|---|
直接 autowire LlmProviderFactory | 绕过租户 + entitlement + 审计;过不了平台代码评审 |
硬编码 providerCode = "openai" | Plugin 用不上客户自有模型部署 |
包成 RuntimeException("LLM failed") 抛 | 丢了 entitlement 与 Provider 错误的区分 |
紧循环里 chat(...) 不 backoff | 触发平台限速;accessor 的 host 端 backoff 不是无限的 |
存 rawResponseJson 给下游解析 | Provider 响应结构会变,用强类型字段 |
与其他 SPI 的关系
CommandHandlerExtension— 接入 Command 管线的方式;通常在这里调chat()FileAccessor— host-bridge 同款模式,用于文件字节BackgroundAccessor— host-bridge 同款模式,用于后台任务系统字段Agent 就绪— command 表面上的agentHint/riskLevel/idempotent与 command 内部的 LLM 调用如何呼应
企业版能力 — 多租户 LLM 预算上限、prompt-provenance 签名、跨租户 Agent Control Plane 编排、Observability Pro 的按调用延迟看板,均为商业版能力。社区版
AiProviderAccessor接口在所有版本一致;围绕它的运维表面不同。
下一步
- Command 管线 —— 围绕
chat()调用的管线阶段 - Agent 就绪 —— 设计可被 LLM 安全调用的命令
- CommandHandlerExtension ——
chat()最常见的承载点 - Plugin Manifest —— 在 Plugin 里声明 AI use-case